Browse Source

Add a copy of scripts from dotnet-deb-tool 2.0.0 to this repo (#1442)

We currently build .deb files using dotnet-deb-tool, which comes from a package feed. We're not completely sure where the source code is for this tool, so this moves the scripts from the dotnet-deb-tool 2.0.0 package into this repo for safe keeping.
Nate McMaster 7 years ago
parent
commit
f2691ee4b6
29 changed files with 2129 additions and 19 deletions
  1. 37 0
      THIRD-PARTY-NOTICES
  2. 0 1
      build/SharedFx.props
  3. 4 5
      build/SharedFxInstaller.targets
  4. 0 13
      build/tools/dotnet-deb-tool-consumer/dotnet-deb-tool-consumer.csproj
  5. 272 0
      src/Installers/Debian/build.sh
  6. 1 0
      src/Installers/Debian/package_files/debian/compat
  7. 1 0
      src/Installers/Debian/package_files/debian/source/format
  8. 267 0
      src/Installers/Debian/scripts/config_template_generator.py
  9. 170 0
      src/Installers/Debian/scripts/debian_build_lib.sh
  10. 64 0
      src/Installers/Debian/scripts/extract_json_value.py
  11. 303 0
      src/Installers/Debian/scripts/manpage_generator.py
  12. 20 0
      src/Installers/Debian/setup/build_setup.sh
  13. 27 0
      src/Installers/Debian/setup/test_setup.sh
  14. 5 0
      src/Installers/Debian/templates/debian/changelog
  15. 19 0
      src/Installers/Debian/templates/debian/control
  16. 10 0
      src/Installers/Debian/templates/debian/copyright
  17. 11 0
      src/Installers/Debian/templates/debian/rules
  18. 41 0
      src/Installers/Debian/test.sh
  19. 80 0
      src/Installers/Debian/test/integration_tests/test_package.bats
  20. 99 0
      src/Installers/Debian/test/test_assets/lkgtestman.1
  21. 40 0
      src/Installers/Debian/test/test_assets/test_package_layout/debian_config.json
  22. 108 0
      src/Installers/Debian/test/test_assets/test_package_layout/docs.json
  23. 99 0
      src/Installers/Debian/test/test_assets/test_package_layout/docs/testdocs.1
  24. 17 0
      src/Installers/Debian/test/test_assets/test_package_layout/package_root/path_relative_to_package_root/test_exe.sh
  25. 7 0
      src/Installers/Debian/test/test_assets/test_package_layout/package_root/test_called.sh
  26. 12 0
      src/Installers/Debian/test/test_assets/test_package_layout/samples/testsample.cs
  27. 108 0
      src/Installers/Debian/test/test_assets/testdocs.json
  28. 279 0
      src/Installers/Debian/test/unit_tests/test_debian_build_lib.bats
  29. 28 0
      src/Installers/Debian/test/unit_tests/test_scripts.bats

+ 37 - 0
THIRD-PARTY-NOTICES

@@ -0,0 +1,37 @@
+.NET Core uses third-party libraries or other resources that may be
+distributed under licenses different than the .NET Core software.
+
+In the event that we accidentally failed to list a required notice, please
+bring it to our attention. Post an issue or email us:
+
+           [email protected]
+
+The attached notices are provided for information only.
+
+License notice for dotnet-deb-tool
+------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) .NET Foundation and Contributors
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 0 - 1
build/SharedFx.props

@@ -8,7 +8,6 @@
     <_TemplatesDir>$(MSBuildThisFileDirectory)tools\templates\</_TemplatesDir>
     <_DockerDir>$(MSBuildThisFileDirectory)tools\docker\</_DockerDir>
     <_PackagingDir>$(MSBuildThisFileDirectory)tools\packaging\</_PackagingDir>
-    <_DebToolDir>$(MSBuildThisFileDirectory)tools\dotnet-deb-tool-consumer\</_DebToolDir>
     <_SharedFxSourceDir>$(RepositoryRoot).deps\Signed\SharedFx\</_SharedFxSourceDir>
     <_InstallerSourceDir>$(RepositoryRoot).deps\Installers\</_InstallerSourceDir>
     <_SymbolsSourceDir>$(RepositoryRoot).deps\symbols\</_SymbolsSourceDir>

+ 4 - 5
build/SharedFxInstaller.targets

@@ -165,13 +165,12 @@
   </Target>
 
   <Target Name="RunDebTool">
-    <!-- Install dotnet-deb tool -->
-    <MSBuild Projects="$(_DebToolDir)dotnet-deb-tool-consumer.csproj" Targets="Restore" />
+    <PropertyGroup>
+      <BuildDebInstallerScript>$(RepositoryRoot)src/Installers/Debian/build.sh</BuildDebInstallerScript>
+    </PropertyGroup>
 
     <!-- Build deb package -->
-    <Exec
-      Command="dotnet deb-tool -i $(_WorkLayoutDir) -o $(_WorkOutputDir) -n $(INSTALLER_NAME) -v $(INSTALLER_VERSION)"
-      WorkingDirectory="$(_DebToolDir)" />
+    <Exec Command="$(BuildDebInstallerScript) -i $(_WorkLayoutDir) -o $(_WorkOutputDir) -n $(INSTALLER_NAME) -v $(INSTALLER_VERSION)" />
   </Target>
 
   <Target Name="_GenerateDeb">

+ 0 - 13
build/tools/dotnet-deb-tool-consumer/dotnet-deb-tool-consumer.csproj

@@ -1,13 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netcoreapp2.0</TargetFramework>
-    <RestoreSources>$(RestoreSources);https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</RestoreSources>
-    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <DotNetCliToolReference Include="dotnet-deb-tool" Version="2.0.0" />
-  </ItemGroup>
-
-</Project>

+ 272 - 0
src/Installers/Debian/build.sh

@@ -0,0 +1,272 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+set -e
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+## Load Functions ##
+source $SCRIPT_DIR/scripts/debian_build_lib.sh
+
+## Debian Package Creation Functions ##
+execute(){
+    if ! parse_args_and_set_env_vars "$@"; then
+        exit 1
+    fi
+
+    # Exit if required validation fails
+    if ! validate_inputs; then
+        exit 1
+    fi
+
+    parse_config_and_set_env_vars
+    clean_or_create_build_dirs
+    package_all
+    generate_all
+    create_source_tarball
+    
+    # Actually Build Package Files
+    (cd ${PACKAGE_SOURCE_DIR}; debuild -us -uc)
+
+    copy_files_to_output
+}
+
+parse_args_and_set_env_vars(){
+    OPTIND=1 # Reset in case getopts has been used previously in the shell.
+
+    while getopts ":n:v:i:o:h" opt; do
+      case $opt in
+        n)
+          export PACKAGE_NAME="$OPTARG"
+          ;;
+        v)
+          export PACKAGE_VERSION="$OPTARG"
+          ;;
+        i)
+          export INPUT_DIR="$OPTARG"
+          ;;
+        o)
+          export OUTPUT_DIR="$OPTARG"
+          ;;
+        h)
+          print_help
+          return 1
+          ;;
+        \?)
+          echo "Invalid option: -$OPTARG" >&2
+          return 1
+          ;;
+        :)
+          echo "Option -$OPTARG requires an argument." >&2
+          return 1
+          ;;
+      esac
+    done
+    
+     # Special Input Directories + Paths
+    ABSOLUTE_PLACEMENT_DIR="${INPUT_DIR}/\$"
+    PACKAGE_ROOT_PLACEMENT_DIR="${INPUT_DIR}/package_root"
+    CONFIG="$INPUT_DIR/debian_config.json"
+
+    return 0
+}
+
+print_help(){
+    echo "Usage: package_tool [-i <INPUT_DIR>] [-o <OUTPUT_DIRECTORY>] 
+    [-n <PACKAGE_NAME>] [-v <PACKAGE_VERSION>] [-h]
+
+    REQUIRED:
+        -i <INPUT_DIR>: Input directory conforming to package_tool conventions and debian_config.json
+        -o <OUTPUT_DIR>: Output directory for debian package and other artifacts
+
+    OPTIONAL:
+        -n <PACKAGE_NAME>: name of created package, will override value in debian_config.json
+        -v <PACKAGE_VERSION>: version of created package, will override value in debian_config.json
+        -h: Show this message
+
+    NOTES:
+        See Readme for more information on package_tool conventions and debian_config.json format
+        https://github.com/dotnet/cli/tree/master/packaging/debian/package_tool
+    "
+}
+
+validate_inputs(){
+    local ret=0
+    if [[ -z "$INPUT_DIR" ]]; then
+        echo "ERROR: -i <INPUT_DIRECTORY> Not Specified"
+        ret=1
+    fi
+
+    if [[ -z "$OUTPUT_DIR" ]]; then
+        echo "ERROR: -o <OUTPUT_DIRECTORY> Not Specified."
+        ret=1
+    fi
+
+    if [[ ! -d "$PACKAGE_ROOT_PLACEMENT_DIR" ]]; then
+        echo "ERROR: package_root directory does not exist"
+        echo $PACKAGE_ROOT_PLACEMENT_DIR
+        ret=1
+    fi
+
+    if [[ ! -f "$CONFIG" ]]; then
+        echo "ERROR: debian_config.json file does not exist"
+        echo $CONFIG
+        ret=1
+    fi
+    
+    return $ret
+}
+
+parse_config_and_set_env_vars(){
+    extract_base_cmd="python $SCRIPT_DIR/scripts/extract_json_value.py"
+    
+    # Arguments Take Precedence over Config
+    [ -z "$PACKAGE_VERSION" ] && PACKAGE_VERSION="$($extract_base_cmd $CONFIG "release.package_version")"
+    [ -z "$PACKAGE_NAME" ] && PACKAGE_NAME="$($extract_base_cmd $CONFIG "package_name")"
+
+    # Inputs
+    INPUT_SAMPLES_DIR="$INPUT_DIR/samples"
+    INPUT_DOCS_DIR="$INPUT_DIR/docs"
+    DOCS_JSON_PATH="$INPUT_DIR/docs.json"
+
+    PACKAGE_SOURCE_DIR="${OUTPUT_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}"
+
+    if ! INSTALL_ROOT="$($extract_base_cmd $CONFIG "install_root")"; then
+        INSTALL_ROOT="/usr/share/$PACKAGE_NAME"
+    fi
+
+    DEBIAN_DIR="${PACKAGE_SOURCE_DIR}/debian"
+    DOCS_DIR="${PACKAGE_SOURCE_DIR}/docs"
+}
+
+clean_or_create_build_dirs(){
+    rm -rf ${PACKAGE_SOURCE_DIR}
+    mkdir -p $DEBIAN_DIR
+}
+
+package_all(){
+    package_static_files
+    package_package_root_placement
+    package_absolute_placement
+    package_samples
+    package_docs
+    package_install_scripts 
+}
+
+generate_all(){
+    generate_config_templates
+    generate_manpages
+    generate_manpage_manifest
+    generate_sample_manifest
+    write_debian_install_file
+}
+
+create_source_tarball(){
+    rm -f ${OUTPUT_DIR}/${PACKAGE_NAME}_${PACKAGE_VERSION}.orig.tar.gz
+    tar -cvzf ${OUTPUT_DIR}/${PACKAGE_NAME}_${PACKAGE_VERSION}.orig.tar.gz -C $PACKAGE_SOURCE_DIR .
+}
+
+copy_files_to_output(){
+    # .deb, .dsc, etc.. Already in output dir
+    # Copy Test files
+
+    cp $SCRIPT_DIR/test/integration_tests/test_package.bats $OUTPUT_DIR
+}
+
+## Packaging Functions ##
+package_static_files(){
+    cp -a $SCRIPT_DIR/package_files/debian/* ${PACKAGE_SOURCE_DIR}/debian
+}
+
+package_package_root_placement(){
+    add_dir_to_install ${PACKAGE_ROOT_PLACEMENT_DIR} ""
+}
+
+package_absolute_placement(){
+    if [[ -d "$ABSOLUTE_PLACEMENT_DIR" ]]; then
+        abs_in_package_dir="\$"
+    
+        add_dir_to_install ${ABSOLUTE_PLACEMENT_DIR} $abs_in_package_dir
+    
+        # Get List of all files in directory tree, relative to ABSOLUTE_PLACEMENT_DIR
+        abs_files=( $(_get_files_in_dir_tree $ABSOLUTE_PLACEMENT_DIR) )
+    
+        # For each file add a a system placement
+        for abs_file in ${abs_files[@]}
+        do
+            parent_dir=$(dirname $abs_file)
+            filename=$(basename $abs_file)
+    
+            add_system_file_placement "$abs_in_package_dir/$abs_file" "/$parent_dir"
+        done
+    fi
+}
+
+package_samples(){
+    if [[ -d "$INPUT_SAMPLES_DIR" ]]; then
+        cp -a $INPUT_SAMPLES_DIR/. $PACKAGE_SOURCE_DIR
+    fi
+}
+
+package_docs(){
+    if [[ -d "$INPUT_DOCS_DIR" ]]; then
+        mkdir -p $DOCS_DIR
+        cp -a $INPUT_DOCS_DIR/. $DOCS_DIR
+    fi
+}
+
+package_install_scripts(){
+    # copy scripts for the package's control section like preinst, postint, etc
+    if [[ -d "$INPUT_DIR/debian" ]]; then
+        cp -a "$INPUT_DIR/debian/." $DEBIAN_DIR
+    fi
+}
+
+## Generation Functions ##
+generate_config_templates(){
+    python ${SCRIPT_DIR}/scripts/config_template_generator.py $CONFIG $SCRIPT_DIR/templates/debian $DEBIAN_DIR $PACKAGE_NAME $PACKAGE_VERSION
+}
+
+generate_manpages(){
+    if [[ -f "$DOCS_JSON_PATH" ]]; then
+        mkdir -p $DOCS_DIR
+        
+        # Generate the manpages from json spec
+        python ${SCRIPT_DIR}/scripts/manpage_generator.py ${DOCS_JSON_PATH} ${DOCS_DIR}
+    fi
+}
+
+generate_manpage_manifest(){
+    # Get a list of files generated relative to $DOCS_DIR
+    generated_manpages=( $(_get_files_in_dir_tree $DOCS_DIR) )
+
+    # Get path relative to $PACKAGE_SOURCE_DIR to prepend to each filename
+    # This syntax is bash substring removal
+    docs_rel_path=${DOCS_DIR#${PACKAGE_SOURCE_DIR}/}
+
+    # Remove any existing manifest
+    rm -f ${DEBIAN_DIR}/${PACKAGE_NAME}.manpages
+
+    for manpage in ${generated_manpages[@]}
+    do
+        echo "${docs_rel_path}/${manpage}" >> "${DEBIAN_DIR}/${PACKAGE_NAME}.manpages"
+    done
+}
+
+generate_sample_manifest(){
+    if [[ -d "$INPUT_SAMPLES_DIR" ]]; then
+        generated_manpages=( $(_get_files_in_dir_tree $INPUT_SAMPLES_DIR) )
+
+        rm -f sample_manifest
+        for sample in ${samples[@]}
+        do
+            echo "$sample" >> "${DEBIAN_DIR}/${PACKAGE_NAME}.examples"
+        done
+    else
+        echo "Provide a 'samples' directory in INPUT_DIR to package samples"
+    fi
+}
+
+execute "$@"

+ 1 - 0
src/Installers/Debian/package_files/debian/compat

@@ -0,0 +1 @@
+9

+ 1 - 0
src/Installers/Debian/package_files/debian/source/format

@@ -0,0 +1 @@
+3.0 (quilt)

+ 267 - 0
src/Installers/Debian/scripts/config_template_generator.py

@@ -0,0 +1,267 @@
+#!/usr/bin/python
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# Parses debian_config.json and generates appropriate templates
+# Where optional defaults exist, they are defined in the template_dict
+#     of the appropriate generation function
+
+import os
+import sys
+import json
+import datetime
+
+FILE_CHANGELOG = 'changelog'
+FILE_CONTROL = 'control'
+FILE_COPYRIGHT = 'copyright'
+FILE_SYMLINK_FORMAT = '{package_name}.links'
+FILE_RULES = 'rules'
+
+PACKAGE_ROOT_FORMAT = "usr/share/{package_name}"
+CHANGELOG_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S %z"
+
+# UTC Timezone for Changelog date
+class UTC(datetime.tzinfo):
+    def utcoffset(self, dt):
+        return datetime.timedelta(0)
+
+    def tzname(self, dt):
+        return "UTC"
+
+    def dst(self, dt):
+        return datetime.timedelta(0)
+
+# Generation Functions
+def generate_and_write_all(config_data, template_dir, output_dir, package_name=None, package_version=None):
+    try:
+        changelog_contents = generate_changelog(
+            config_data, 
+            template_dir, 
+            package_name=package_name, 
+            package_version=package_version)
+
+        control_contents = generate_control(config_data, template_dir, package_name=package_name)
+        copyright_contents = generate_copyright(config_data, template_dir)
+        symlink_contents = generate_symlinks(config_data, package_name=package_name)
+        rules_contents = generate_rules(config_data, template_dir)
+    except Exception as exc:
+      print exc
+      help_and_exit("Error: Generation Failed, check your config file.")
+
+    write_file(changelog_contents, output_dir, FILE_CHANGELOG)
+    write_file(control_contents, output_dir, FILE_CONTROL)
+    write_file(copyright_contents, output_dir, FILE_COPYRIGHT)
+    write_file(rules_contents, output_dir, FILE_RULES)
+
+    # Symlink File is optional
+    if symlink_contents:
+        symlink_filename = get_symlink_filename(config_data, package_name=package_name)
+        write_file(symlink_contents, output_dir, symlink_filename)
+
+    return
+
+def generate_rules(config_data, template_dir):
+    template = get_template(template_dir, FILE_RULES)
+
+    ignored_dependency_packages = config_data.get("debian_ignored_dependencies", None)
+    override_text = ""
+
+    if ignored_dependency_packages != None:
+        override_text = "override_dh_shlibdeps:\n\tdh_shlibdeps --dpkg-shlibdeps-params=\""
+
+        for package in ignored_dependency_packages:
+            override_text += "-x{0} ".format(package)
+
+        override_text += "\""
+
+    return template.format(overrides=override_text)
+
+def generate_changelog(config_data, template_dir, package_version=None, package_name=None):
+    template = get_template(template_dir, FILE_CHANGELOG)
+
+    release_data = config_data["release"]
+    
+    # Allow for Version Override
+    config_package_version = release_data["package_version"]
+    package_version = package_version or config_package_version
+
+    template_dict = dict(\
+        PACKAGE_VERSION=package_version,
+        PACKAGE_REVISION=release_data["package_revision"],
+        CHANGELOG_MESSAGE=release_data["changelog_message"],
+        URGENCY=release_data.get("urgency", "low"),
+
+        PACKAGE_NAME=package_name or config_data["package_name"],
+        MAINTAINER_NAME=config_data["maintainer_name"],
+        MAINTAINER_EMAIL=config_data["maintainer_email"], 
+        DATE=datetime.datetime.now(UTC()).strftime(CHANGELOG_DATE_FORMAT)
+    )
+
+    contents = template.format(**template_dict)
+
+    return contents
+
+def generate_control(config_data, template_dir, package_name=None):
+    template = get_template(template_dir, FILE_CONTROL)
+
+    dependency_data = config_data.get("debian_dependencies", None)
+    dependency_str = get_dependendent_packages_string(dependency_data)
+
+    conflict_data = config_data.get("package_conflicts", [])
+    conflict_str = ', '.join(conflict_data)
+
+    # Default to empty dict, so we don't explode on nested optional values
+    control_data = config_data.get("control", dict())
+
+    template_dict = dict(\
+        SHORT_DESCRIPTION=config_data["short_description"],
+        LONG_DESCRIPTION=config_data["long_description"],
+        HOMEPAGE=config_data.get("homepage", ""),
+
+        SECTION=control_data.get("section", "misc"),
+        PRIORITY=control_data.get("priority", "low"),
+        ARCH=control_data.get("architecture", "all"),
+
+        DEPENDENT_PACKAGES=dependency_str,
+        CONFLICT_PACKAGES=conflict_str,
+
+        PACKAGE_NAME=package_name or config_data["package_name"],
+        MAINTAINER_NAME=config_data["maintainer_name"],
+        MAINTAINER_EMAIL=config_data["maintainer_email"]
+    )
+
+    contents = template.format(**template_dict)
+
+    return contents
+
+def generate_copyright(config_data, template_dir):
+    template = get_template(template_dir, FILE_COPYRIGHT)
+
+    license_data = config_data["license"]
+
+    template_dict = dict(\
+        COPYRIGHT_TEXT=config_data["copyright"],
+        LICENSE_NAME=license_data["type"],
+        LICENSE_TEXT=license_data["full_text"]
+    )
+
+    contents = template.format(**template_dict)
+
+    return contents
+
+def generate_symlinks(config_data, package_name=None):
+    symlink_entries = []
+    package_root_path = get_package_root(config_data, package_name=package_name)
+
+    symlink_data = config_data.get("symlinks", dict())
+
+    for package_rel_path, symlink_path in symlink_data.iteritems():
+
+        package_abs_path = os.path.join(package_root_path, package_rel_path)
+
+        symlink_entries.append( '%s %s' % (package_abs_path, symlink_path) )
+
+    return '\n'.join(symlink_entries)
+    
+# Helper Functions
+def get_package_root(config_data, package_name=None):
+    config_install_root = config_data.get("install_root", None)
+    package_name = package_name or config_data["package_name"]
+
+    return config_install_root or PACKAGE_ROOT_FORMAT.format(package_name=package_name)
+
+def get_symlink_filename(config_data, package_name=None):
+    package_name = package_name or config_data["package_name"]
+    return FILE_SYMLINK_FORMAT.format(package_name=package_name)
+
+def get_dependendent_packages_string(debian_dependency_data):
+    if debian_dependency_data is None:
+        return ""
+
+    dependencies = []
+        
+    for debian_package_name in debian_dependency_data:
+        dep_str = debian_package_name
+
+        if debian_dependency_data[debian_package_name].get("package_version", None):
+            debian_package_version = debian_dependency_data[debian_package_name].get("package_version")
+
+            dep_str += " (>= %s)" % debian_package_version
+
+        dependencies.append(dep_str)
+
+    # Leading Comma is important here
+    return ', ' + ', '.join(dependencies)
+
+
+def load_json(json_path):
+    json_data = None
+    with open(json_path, 'r') as json_file:
+        json_data = json.load(json_file)
+
+    return json_data
+
+def get_template(template_dir, name):
+    path = os.path.join(template_dir, name)
+    template_contents = None
+
+    with open(path, 'r') as template_file:
+        template_contents = template_file.read()
+
+    return template_contents
+
+def write_file(contents, output_dir, name):
+    path = os.path.join(output_dir, name)
+
+    with open(path, 'w') as out_file:
+        out_file.write(contents)
+
+    return
+
+# Tool Functions
+def help_and_exit(msg):
+    print msg
+    sys.exit(1)
+
+def print_usage():
+    print "Usage: config_template_generator.py [config file path] [template directory path] [output directory] (package name) (package version)"
+
+def parse_and_validate_args():
+    if len(sys.argv) < 4:
+        print_usage()
+        help_and_exit("Error: Invalid Arguments")
+
+    config_path = sys.argv[1]
+    template_dir = sys.argv[2]
+    output_dir = sys.argv[3]
+    name_override = None
+    version_override = None
+    
+    if len(sys.argv) >= 5:
+        name_override = sys.argv[4]
+
+    if len(sys.argv) >= 6:
+        version_override = sys.argv[5]
+
+    if not os.path.isfile(config_path):
+        help_and_exit("Error: Invalid config file path")
+
+    if not os.path.isdir(template_dir):
+        help_and_exit("Error: Invalid template directory path")
+
+    if not os.path.isdir(output_dir):
+        help_and_exit("Error: Invalid output directory path")
+
+    return (config_path, template_dir, output_dir, name_override, version_override)
+
+def execute():
+    config_path, template_dir, output_dir, name_override, version_override = parse_and_validate_args()
+
+    config_data = load_json(config_path)
+
+    generate_and_write_all(config_data, template_dir, output_dir, package_name = name_override, package_version=version_override)
+
+if __name__ == "__main__":
+    execute()

+ 170 - 0
src/Installers/Debian/scripts/debian_build_lib.sh

@@ -0,0 +1,170 @@
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# This file is not intended to be executed directly
+# Import these functions using source
+#
+# Relies on these environment variables:
+# PACKAGE_SOURCE_DIR :: Package Source Staging Directory
+# INSTALL_ROOT :: Absolute path of package installation root
+
+# write_debian_install_file
+# Summary: Writes the contents of the "install_placement" array to the debian/install
+#   This array is populated by calls to the "add_system_file_placement" function
+# Usage: write_debian_install_file
+write_debian_install_file(){
+    # Remove any existing install file, we need to overwrite it
+    rm -f ${PACKAGE_SOURCE_DIR}/debian/install
+
+    for i in "${install_placement[@]}"
+    do
+        echo "${i}" >> "${PACKAGE_SOURCE_DIR}/debian/install"
+    done
+}
+
+# add_system_file_placement
+# Summary:  Registers a file placement on the filesystem from the package by populating the "install_placement" array
+# Usage: add_system_file_placement {local path of file in package} {absolute path of directory to place file in}
+add_system_file_placement(){
+    #Initialize placement_index variable
+    if [[ -z "$placement_index" ]]; then
+        placement_index=0
+    fi
+
+    install_placement[${placement_index}]="${1} ${2}"
+    placement_index=$((${placement_index}+1))
+}
+
+# add_system_dir_placement
+# Summary: Registers a directory placement on the post-installation package from an in-package path
+add_system_dir_placement(){
+
+    in_package_dir=$1
+    abs_installation_dir=$2
+
+    dir_files=( $(_get_files_in_dir_tree $PACKAGE_SOURCE_DIR/$in_package_dir) )
+    
+    # If in_package_dir isn't empty include a slash
+    if [ ! -z "$in_package_dir" ]; then
+        in_package_dir="${in_package_dir}/"
+    fi
+
+    for rel_filepath in ${dir_files[@]}
+    do
+        local parent_path=$(dirname $rel_filepath)
+
+        # If there is no parent, parent_path = "."
+        if [[ "$parent_path" == "." ]]; then
+            add_system_file_placement "${in_package_dir}${rel_filepath}" "${abs_installation_dir}"
+        else
+            add_system_file_placement "${in_package_dir}${rel_filepath}" "${abs_installation_dir}/${parent_path}"
+        fi
+
+    done
+}
+
+# add_file_to_install
+# Summary: Adds a file from the local filesystem to the package and installs it rooted at INSTALL_ROOT
+# Usage: add_install_file {relative path to local file} {relative path to INSTALL_ROOT to place file}
+add_file_to_install(){
+    copy_from_file=$1
+    rel_install_path=$2
+
+    local filename=$(basename $copy_from_file)
+    local parent_dir=$(dirname $copy_from_file)
+
+    # Create Relative Copy From Path
+    rel_copy_from_file=${copy_from_file#$parent_dir/}
+
+    # Delete any existing file and ensure path exists
+    rm -f ./${PACKAGE_SOURCE_DIR}/${rel_install_path}/${filename}
+    mkdir -p ./${PACKAGE_SOURCE_DIR}/${rel_install_path}
+
+    dir_files=( "$rel_copy_from_file" )
+
+    _copy_files_to_package $parent_dir $rel_install_path "${dir_files[@]}"
+
+    add_system_file_placement "${rel_install_path}/${filename}" "${INSTALL_ROOT}/$rel_install_path"
+}
+
+# add_dir_to_install
+# Summary: Adds contents of a directory on the local filesystem to the package and installs them rooted at INSTALL_ROOT
+#     Note: Does not install the directory passed, only its contents 
+# Usage: add_dir_to_install {relative path of directory to copy} {relative path to INSTALL_ROOT to place directory tree}
+add_dir_to_install(){
+    
+    copy_from_dir=$1
+    rel_install_path=$2
+
+    # Delete and Create any existing directory
+    mkdir -p ${PACKAGE_SOURCE_DIR}/${rel_install_path}
+
+    dir_files=( $(_get_files_in_dir_tree $copy_from_dir) )
+
+    _copy_files_to_package "$copy_from_dir" "$rel_install_path" "${dir_files[@]}"
+
+    for file in "${dir_files[@]}"
+    do
+        file_rel_dir="$(dirname $file)"
+        add_system_file_placement "${rel_install_path}/${file}" "${INSTALL_ROOT}/$rel_install_path/${file_rel_dir}"
+    done
+}
+
+# Usage: _copy_files_to_package {local files root directory} {relative directory in package to copy to} "${filepath_array[@]}"
+# Note: The specific syntax on the parameter shows how to pass an array
+_copy_files_to_package(){
+    local_root_dir=$1
+    package_dest_dir=$2
+
+    # Consume the remaining input as an array
+    shift; shift;
+    rel_filepath_list=( $@ )
+
+    for rel_filepath in ${rel_filepath_list[@]}
+    do
+        local parent_dir=$(dirname $rel_filepath)
+        local filename=$(basename $rel_filepath)
+            
+        mkdir -p ${PACKAGE_SOURCE_DIR}/${package_dest_dir}/${parent_dir}
+
+        # Ignore $parent_dir if it there isn't one
+        if [[ "$parent_dir" == "." ]]; then
+            cp "${local_root_dir}/${rel_filepath}" "${PACKAGE_SOURCE_DIR}/${package_dest_dir}"
+        else
+            cp "${local_root_dir}/${rel_filepath}" "${PACKAGE_SOURCE_DIR}/${package_dest_dir}/${parent_dir}"
+        fi
+        
+    done
+}
+
+# Usage: _get_files_in_dir_tree {path of directory}
+_get_files_in_dir_tree(){
+
+    root_dir=$1
+
+    # Use Globstar expansion to enumerate all directories and files in the tree
+    shopt -s globstar
+    shopt -s dotglob
+    dir_tree_list=( "${root_dir}/"** )
+
+    # Build a new array with only the Files contained in $dir_tree_list
+    local index=0
+    for file_path in "${dir_tree_list[@]}"
+    do
+        if [ -f $file_path ]; then
+            dir_tree_file_list[${index}]=$file_path
+            index=$(($index+1))
+        fi
+    done
+
+    # Remove $root_dir prefix from each path in dir_tree_file_list
+    # This is confusing syntax, so here's a reference link (Substring Removal)
+    #     http://wiki.bash-hackers.org/syntax/pe
+    dir_tree_file_list=( "${dir_tree_file_list[@]#${root_dir}/}" )
+
+    # Echo is the return mechanism
+    echo "${dir_tree_file_list[@]}"
+}
+

+ 64 - 0
src/Installers/Debian/scripts/extract_json_value.py

@@ -0,0 +1,64 @@
+#!/usr/bin/python
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# Extract Json Value
+#
+# Very simple tool to ease extracting json values from the cmd line.
+import os
+import sys
+import json
+
+def print_usage():
+	print """
+		Usage: extract_json_value.py [json file path] [key of value to extract]
+		For nested keys, use . separator
+	"""
+
+def help_and_exit(msg=None):
+	print msg
+	print_usage()
+	sys.exit(1)
+
+def parse_and_validate_args():
+	
+	if len(sys.argv) < 3:
+		help_and_exit(msg="Error: Invalid Args")
+
+	json_path = sys.argv[1]
+	json_key = sys.argv[2]
+
+	if not os.path.isfile(json_path):
+		help_and_exit("Error: Invalid json file path")
+
+	return json_path, json_key
+
+def extract_key(json_path, json_key):
+	json_data = None
+
+	with open(json_path, 'r') as json_file:
+		json_data = json.load(json_file)
+
+	nested_keys = json_key.split('.')
+	json_context = json_data
+
+	for key in nested_keys:
+		json_context = json_context.get(key, None)
+
+		if json_context is None:
+			help_and_exit("Error: Invalid json key")
+
+	return str(json_context)
+
+def execute():
+	json_path, json_key = parse_and_validate_args()
+
+	value = extract_key(json_path, json_key)
+
+	return value
+
+if __name__ == "__main__":
+	print execute()
+

+ 303 - 0
src/Installers/Debian/scripts/manpage_generator.py

@@ -0,0 +1,303 @@
+#!/usr/bin/python
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# manpage_generator
+#       Converts top level docs.json format command info to
+#       nroff manpage format. Done in python for easy json parsing.
+#
+#   Usage: argv[1] = path to docs.json; argv[2] = output path
+
+import sys
+import os
+import json
+import datetime
+
+SECTION_SEPARATOR = "\n.P \n"
+MANPAGE_EXTENSION = ".1"
+
+# For now this is a magic number
+# See https://www.debian.org/doc/manuals/maint-guide/dother.en.html#manpage
+SECTION_NUMBER = 1
+
+def generate_man_pages(doc_path, output_dir):
+
+    with open(doc_path) as doc_file:
+        doc_json = None
+        try:
+            doc_json = json.load(doc_file)
+        except:
+            raise Exception("Failed to load json file. Check formatting.")
+
+        tools = doc_json.get("tools", None)
+
+        if tools is None:
+            raise Exception("No tool sections in doc.json")
+
+        for tool_name in tools:
+            tool_data = tools[tool_name]
+
+            man_page_content = generate_man_page(tool_name, tool_data)
+            man_page_path = get_output_path(tool_name, output_dir)
+
+            write_man_page(man_page_path, man_page_content)
+
+def get_output_path(toolname, output_dir):
+    out_filename = toolname + MANPAGE_EXTENSION
+
+    return os.path.join(output_dir, out_filename)
+
+def write_man_page(path, content):
+    with open(path, 'w') as man_file:
+        man_file.write(content)
+
+	#Build Fails without a final newline
+	man_file.write('\n')
+
+def generate_man_page(tool_name, tool_data):
+
+    sections = [
+            generate_header_section(tool_name, tool_data),
+            generate_name_section(tool_name, tool_data),
+            generate_synopsis_section(tool_name, tool_data),
+            generate_description_section(tool_name, tool_data),
+            generate_options_section(tool_name, tool_data),
+            generate_author_section(tool_name, tool_data),
+            generate_copyright_section(tool_name, tool_data)
+    ]
+
+    return SECTION_SEPARATOR.join(sections)
+
+def generate_header_section(tool_name, tool_data):#
+    roff_text_builder = []
+
+    header_format = ".TH {program_name} {section_number} {center_footer} {left_footer} {center_header}"
+
+    today = datetime.date.today()
+    today_string = today.strftime("%B %d, %Y")
+
+    format_args = {
+            "program_name" : tool_name,
+            "section_number" : SECTION_NUMBER,
+            "center_footer" : "",   # Omitted
+            "left_footer" : "",     # Omitted
+            "center_header" : ""    # Omitted
+    }
+
+    roff_text_builder.append(header_format.format(**format_args))
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def generate_name_section(tool_name, tool_data):#
+    roff_text_builder = []
+    roff_text_builder.append(".SH NAME")
+
+    tool_short_description = tool_data.get("short_description", "")
+    name_format = ".B {program_name} - {short_description}"
+
+    name_format_args = {
+            "program_name": tool_name,
+            "short_description" : tool_short_description
+    }
+
+    roff_text_builder.append(name_format.format(**name_format_args))
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def generate_synopsis_section(tool_name, tool_data):#
+    roff_text_builder = []
+    roff_text_builder.append(".SH SYNOPSIS")
+
+    synopsis_format = '.B {program_name} {command_name} \n.RI {options} " "\n.I "{argument_list_name}"'
+
+    tool_commands = tool_data.get("commands", [])
+    for command_name in tool_commands:
+        command_data = tool_commands[command_name]
+
+        # Default options to empty list so the loop doesn't blow up
+        options = command_data.get("options", [])
+        argument_list = command_data.get("argumentlist", None)
+
+        # Construct Option Strings
+        option_string_list = []
+        argument_list_name = ""
+
+        for option_name in options:
+            option_data = options[option_name]
+
+            specifier_short = option_data.get("short", None)
+            specifier_long = option_data.get("long", None)
+            parameter = option_data.get("parameter", None)
+
+            option_string = _option_string_helper(specifier_short, specifier_long, parameter)
+
+            option_string_list.append(option_string)
+
+        # Populate Argument List Name
+        if argument_list:
+            argument_list_name = argument_list.get("name", "")
+
+        cmd_format_args = {
+                'program_name' : tool_name,
+                'command_name' : command_name,
+                'options' : '" "'.join(option_string_list),
+                'argument_list_name' : argument_list_name
+        }
+
+        cmd_string = synopsis_format.format(**cmd_format_args)
+
+        roff_text_builder.append(cmd_string)
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def generate_description_section(tool_name, tool_data):#
+    roff_text_builder = []
+    roff_text_builder.append(".SH DESCRIPTION")
+
+    # Tool Description
+    long_description = tool_data.get("long_description", "")
+    roff_text_builder.append(".PP {0}".format(long_description))
+
+    # Command Descriptions
+    cmd_description_format = ".B {program_name} {command_name}\n{command_description}"
+
+    tool_commands = tool_data.get("commands", [])
+    for command_name in tool_commands:
+        command_data = tool_commands[command_name]
+
+        command_description = command_data.get("description", "")
+
+        format_args = {
+            "program_name" : tool_name,
+            "command_name" : command_name,
+            "command_description" : command_description
+        }
+
+        cmd_string = cmd_description_format.format(**format_args)
+
+        roff_text_builder.append(cmd_string)
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def generate_options_section(tool_name, tool_data):#
+    roff_text_builder = []
+    roff_text_builder.append(".SH OPTIONS")
+
+    options_format = '.TP\n.B {option_specifiers}\n{option_description}'
+
+    tool_commands = tool_data.get("commands", [])
+    for command_name in tool_commands:
+        command_data = tool_commands[command_name]
+
+        # Default to empty list so the loop doesn't blow up
+        options = command_data.get("options", [])
+
+        for option_name in options:
+            option_data = options[option_name]
+
+            specifier_short = option_data.get("short", None)
+            specifier_long = option_data.get("long", None)
+            parameter = option_data.get("parameter", None)
+            description = option_data.get("description", "")
+
+            option_specifiers_string = _option_string_helper(specifier_short, 
+                specifier_long, 
+                parameter, 
+                include_brackets = False, 
+                delimiter=' ", " ')
+
+            format_args = {
+                "option_specifiers": option_specifiers_string,
+                "option_description" : description
+            }
+
+            roff_text_builder.append(options_format.format(**format_args))
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def generate_author_section(tool_name, tool_data):#
+    roff_text_builder = []
+    roff_text_builder.append(".SH AUTHOR")
+    
+    author_format = '.B "{author_name}" " " \n.RI ( "{author_email}" )'
+    
+    author_name = tool_data.get("author", "")
+    author_email = tool_data.get("author_email", "")
+    
+    format_args = {
+        "author_name" : author_name,
+        "author_email" : author_email
+    }
+    
+    roff_text_builder.append(author_format.format(**format_args))
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def generate_copyright_section(tool_name, tool_data):#
+    roff_text_builder = []
+    roff_text_builder.append(".SH COPYRIGHT")
+    
+    copyright_data = tool_data.get("copyright")
+    
+    roff_text_builder.append('.B "{0}"'.format(copyright_data))
+
+    return SECTION_SEPARATOR.join(roff_text_builder)
+
+def _option_string_helper(specifier_short, specifier_long, parameter, include_brackets = True, delimiter = " | "):
+    option_string = ""
+
+    if include_brackets:
+        option_string = " [ "
+
+    if specifier_short:
+        option_string += ' "{0}" '.format(specifier_short)
+
+    if specifier_short and specifier_long:
+        option_string += delimiter
+
+    if specifier_long:
+        option_string += ' "{0}" '.format(specifier_long)
+
+    if parameter:
+    	option_string += ' " " '
+        option_string += ' "{0}" '.format(parameter)
+
+    if include_brackets:
+        option_string += " ] "
+
+    return option_string
+
+
+def print_usage():
+    print "Usage: argv[1] = path to docs.json; argv[2] = output path"
+    print "Example: manpage_generator.py ../docs.json ./dotnet-1.0/debian"
+
+def parse_args():
+    doc_path = sys.argv[1]
+    output_dir = sys.argv[2]
+
+    return (doc_path, output_dir)
+
+def validate_args(doc_path, output_dir):
+    if not os.path.isfile(doc_path):
+        raise Exception("Docs.json path is not valid.")
+
+    if not os.path.isdir(output_dir):
+        raise Exception("Output Directory Path is not valid.")
+
+def execute_command_line():
+    try:
+        doc_path, output_dir = parse_args()
+
+        validate_args(doc_path, output_dir)
+
+        generate_man_pages(doc_path, output_dir)
+
+    except Exception as exc:
+        print "Error: ", exc
+        print_usage()
+
+if __name__ == "__main__":
+    execute_command_line()

+ 20 - 0
src/Installers/Debian/setup/build_setup.sh

@@ -0,0 +1,20 @@
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+install_dependencies(){
+    # Add LLdb 3.6 package source
+    echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | tee /etc/apt/sources.list.d/llvm.list
+    wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | apt-key add -
+
+    #Install Deps
+    apt-get update
+    apt-get install -y debhelper build-essential devscripts git liblttng-ust-dev lldb-3.6-dev
+}
+
+setup(){
+    install_dependencies
+}
+
+setup

+ 27 - 0
src/Installers/Debian/setup/test_setup.sh

@@ -0,0 +1,27 @@
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+install_dependencies(){
+    # Add LLdb 3.6 package source
+    echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | tee /etc/apt/sources.list.d/llvm.list
+    wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | apt-key add -
+
+    #Install Deps
+    apt-get update
+    apt-get install -y debhelper build-essential devscripts git liblttng-ust-dev lldb-3.6-dev
+}
+
+install_bats(){
+    git clone https://github.com/sstephenson/bats.git
+    cd bats
+    ./install.sh /usr/local
+}
+
+setup(){
+    install_dependencies
+    install_bats
+}
+
+setup

+ 5 - 0
src/Installers/Debian/templates/debian/changelog

@@ -0,0 +1,5 @@
+{PACKAGE_NAME} ({PACKAGE_VERSION}-{PACKAGE_REVISION}) unstable; urgency={URGENCY}
+
+  * {CHANGELOG_MESSAGE}
+
+ -- {MAINTAINER_NAME} <{MAINTAINER_EMAIL}>  {DATE}

+ 19 - 0
src/Installers/Debian/templates/debian/control

@@ -0,0 +1,19 @@
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+Source: {PACKAGE_NAME}
+Maintainer: {MAINTAINER_NAME} <{MAINTAINER_EMAIL}>
+Section: {SECTION}
+Priority: {PRIORITY}
+Standards-Version: 3.9.2
+Build-Depends: debhelper (>=9)
+Homepage: {HOMEPAGE}
+
+Package: {PACKAGE_NAME}
+Architecture: {ARCH}
+Depends: ${{shlibs:Depends}}, ${{misc:Depends}}{DEPENDENT_PACKAGES}
+Conflicts: {CONFLICT_PACKAGES}
+Description: {SHORT_DESCRIPTION}
+ {LONG_DESCRIPTION}

+ 10 - 0
src/Installers/Debian/templates/debian/copyright

@@ -0,0 +1,10 @@
+Comment: Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: *
+Copyright: {COPYRIGHT_TEXT}
+License: {LICENSE_NAME}
+
+License: {LICENSE_NAME}
+ {LICENSE_TEXT}

+ 11 - 0
src/Installers/Debian/templates/debian/rules

@@ -0,0 +1,11 @@
+#!/usr/bin/make -f
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+{overrides}
+
+%:
+	dh $@
+

+ 41 - 0
src/Installers/Debian/test.sh

@@ -0,0 +1,41 @@
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+current_user=$(whoami)
+if [ $current_user != "root" ]; then
+    echo "test.sh requires superuser privileges to run"
+    exit 1
+fi
+
+run_unit_tests(){
+    bats $DIR/test/unit_tests/test_debian_build_lib.bats
+    bats $DIR/test/unit_tests/test_scripts.bats
+}
+
+run_integration_tests(){
+    input_dir=$DIR/test/test_assets/test_package_layout
+    output_dir=$DIR/bin
+
+    # Create output dir
+    mkdir -p $output_dir
+
+    # Build the actual package
+    $DIR/package_tool -i $input_dir -o $output_dir
+
+    # Integration Test Entrypoint placed by package_tool
+    bats $output_dir/test_package.bats
+
+    # Cleanup output dir
+    rm -rf $DIR/test/test_assets/test_package_output
+}
+
+run_all(){
+    run_unit_tests
+    run_integration_tests
+}
+
+run_all

+ 80 - 0
src/Installers/Debian/test/integration_tests/test_package.bats

@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+# This script is used to test the debian package after it's creation.
+# The package tool will drop it next to the .deb file it creates.
+# Environment Variables:
+#   LAST_VERSION_URL: Url for last version .deb (to test upgrades) [required for upgrade test]
+
+#Ensure running with superuser privileges
+current_user=$(whoami)
+if [ $current_user != "root" ]; then
+	echo "WARNING: test_package.bats requires superuser privileges to run, trying sudo..."
+	SUDO_PREFIX="sudo"
+fi
+
+setup(){
+	DIR="$BATS_TEST_DIRNAME"
+
+	PACKAGE_FILENAME="$(ls $DIR | grep .deb -m 1)"
+	PACKAGE_PATH="$DIR/*.deb"
+
+	# Get Package name from package path, 
+	PACKAGE_NAME=${PACKAGE_FILENAME%%_*}
+}
+
+install_package(){
+	$SUDO_PREFIX dpkg -i $PACKAGE_PATH
+}
+
+remove_package(){
+	$SUDO_PREFIX dpkg -r $PACKAGE_NAME
+}
+
+purge_package(){
+	$SUDO_PREFIX dpkg -P $PACKAGE_NAME
+}
+
+install_last_version(){
+	$SUDO_PREFIX dpkg -i "$DIR/last_version.deb"
+}
+
+download_and_install_last_version(){
+	curl "$LAST_VERSION_URL" -o "$DIR/last_version.deb"
+	
+	install_last_version
+}
+
+delete_last_version(){
+        rm -f "$DIR/last_version.deb"
+}
+
+teardown(){
+        delete_last_version
+}
+
+@test "package install + removal test" {
+	install_package
+	remove_package
+}
+
+@test "package install + purge test" {
+	install_package
+	purge_package
+}
+
+# Ultimate Package Test
+# https://www.debian.org/doc/manuals/maint-guide/checkit.en.html#pmaintscripts
+@test "package install + upgrade + purge + install + remove + install + purge test" {
+	if [ ! -z "$LAST_VERSION_URL" ]; then
+		download_and_install_last_version
+		install_package
+		purge_package
+		install_package
+		remove_package
+		install_package
+		purge_package
+	fi
+}

+ 99 - 0
src/Installers/Debian/test/test_assets/lkgtestman.1

@@ -0,0 +1,99 @@
+.TH tool1 1   
+.P 
+.SH NAME
+.P 
+.B tool1 - A tool to Vestibulum lacinia arcu eget nulla.
+.P 
+.SH SYNOPSIS
+.P 
+.B tool1 noOptionsCommand 
+.RI  " "
+.I "argument list stuff"
+.P 
+.B tool1 noArgumentListFullOptions 
+.RI  [  "-t"  |  "--target"  " "  "TARGET"  ] " " [  "--noshortparam"  ] " " [  "-l"  ] " " [  "-p"  |  "--noparam"  ] " " [  "-n"  " "  "NOLONG"  ] " " [  "--noshort"  " "  "NOSHORT"  ] " " [  "-a"  |  "--another"  " "  "ANOTHER"  ]  " "
+.I ""
+.P 
+.B tool1 fullcommand1 
+.RI  [  "-t"  |  "--target"  " "  "TARGET"  ] " " [  "--noshortparam"  ] " " [  "-l"  ] " " [  "-p"  |  "--noparam"  ] " " [  "-n"  " "  "NOLONG"  ] " " [  "--noshort"  " "  "NOSHORT"  ] " " [  "-a"  |  "--another"  " "  "ANOTHER"  ]  " "
+.I "argument list stuff"
+.P 
+.SH DESCRIPTION
+.P 
+.PP Tool1 is a really great Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. 
+.P 
+.B tool1 noOptionsCommand
+builds a native executable from specified source files
+.P 
+.B tool1 noArgumentListFullOptions
+builds a native executable from specified source files
+.P 
+.B tool1 fullcommand1
+builds a native executable from specified source files
+.P 
+.SH OPTIONS
+.P 
+.TP
+.B  "-t"  ", "  "--target"  " "  "TARGET" 
+Specifies target binary output type. EXE or DLL
+.P 
+.TP
+.B  "--noshortparam" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.TP
+.B  "-l" 
+Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet
+.P 
+.TP
+.B  "-p"  ", "  "--noparam" 
+Specifies noparam metus vitae pharetra auctor, sem
+.P 
+.TP
+.B  "-n"  " "  "NOLONG" 
+Specifies nolong Nunc feugiat mi a tellus consequat 
+.P 
+.TP
+.B  "--noshort"  " "  "NOSHORT" 
+Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt
+.P 
+.TP
+.B  "-a"  ", "  "--another"  " "  "ANOTHER" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.TP
+.B  "-t"  ", "  "--target"  " "  "TARGET" 
+Specifies target binary output type. EXE or DLL
+.P 
+.TP
+.B  "--noshortparam" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.TP
+.B  "-l" 
+Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet
+.P 
+.TP
+.B  "-p"  ", "  "--noparam" 
+Specifies noparam metus vitae pharetra auctor, sem
+.P 
+.TP
+.B  "-n"  " "  "NOLONG" 
+Specifies nolong Nunc feugiat mi a tellus consequat 
+.P 
+.TP
+.B  "--noshort"  " "  "NOSHORT" 
+Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt
+.P 
+.TP
+.B  "-a"  ", "  "--another"  " "  "ANOTHER" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.SH AUTHOR
+.P 
+.B "Test Author" " " 
+.RI ( "[email protected]" )
+.P 
+.SH COPYRIGHT
+.P 
+.B "This is the copyright of tool1."

+ 40 - 0
src/Installers/Debian/test/test_assets/test_package_layout/debian_config.json

@@ -0,0 +1,40 @@
+{
+    "maintainer_name":"Microsoft",
+    "maintainer_email": "[email protected]",        
+
+    "package_name": "packagetooltest",                           
+
+    "short_description": "This is a test package",              
+    "long_description": "This is a longer description of the test package", 
+    "homepage": "http://testpackage.com",                        
+
+    "release":{
+        "package_version":"0.1",                               
+        "package_revision":"1",                                 
+        "urgency" : "low",                                      
+        "changelog_message" : "some stuff here"                
+    },
+
+    "control": {                                               
+        "priority":"standard",                                  
+        "section":"devel",                                      
+        "architecture":"any"                                    
+    },
+
+    "copyright": "2015 Microsoft",                              
+    "license": {                                                
+        "type": "some_license",                                 
+        "full_text": "full license text here"                   
+    },
+
+    "debian_dependencies" : {                                   
+        "curl": {
+            "package_version" : "0.5.3"                         
+        },
+        "python":{}
+    }, 
+
+    "symlinks": {                                               
+        "path_relative_to_package_root/test_exe.sh" : "usr/bin/test_exe.sh" 
+    }
+}

+ 108 - 0
src/Installers/Debian/test/test_assets/test_package_layout/docs.json

@@ -0,0 +1,108 @@
+{
+    "tools": {
+        "tool1": { 
+            "copyright": "This is the copyright of tool1.",
+            "license" : "This is the license of tool1",
+            "author":"Test Author",
+            "author_email":"[email protected]",
+            "short_description":"A tool to Vestibulum lacinia arcu eget nulla.",
+            "long_description":"Tool1 is a really great Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. ",
+            "commands": {     
+                "fullcommand1":{
+                    "description":"builds a native executable from specified source files",
+                    "options" : {
+                        "target": {
+                            "short": "-t",
+                            "long":"--target",
+                            "description":"Specifies target binary output type. EXE or DLL",
+                            "parameter":"TARGET"
+                        },
+                        "target2": {
+                            "short": "-a",
+                            "long":"--another",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra",
+                            "parameter":"ANOTHER"
+                        },
+                        "noshort": {
+                            "long":"--noshort",
+                            "description":"Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt",
+                            "parameter":"NOSHORT"
+                        },
+                        "nolong": {
+                            "short": "-n",
+                            "description":"Specifies nolong Nunc feugiat mi a tellus consequat ",
+                            "parameter":"NOLONG"
+                        },
+                        "noparam": {
+                            "short": "-p",
+                            "long":"--noparam",
+                            "description":"Specifies noparam metus vitae pharetra auctor, sem"
+                        },
+                        "noParamNoShort": {
+                            "long":"--noshortparam",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra"
+                        },
+                        "noParamNoLong": {
+                            "short": "-l",
+                            "description":"Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet"
+                        }
+                    },
+                    "argumentlist" : {
+                        "name":"argument list stuff",
+                        "description":"ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;"
+                    }
+                    
+                },
+                "noOptionsCommand":{
+                    "description":"builds a native executable from specified source files",
+                    "argumentlist" : {
+                        "name":"argument list stuff",
+                        "description":"ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;"
+                    }
+                    
+                },
+                "noArgumentListFullOptions":{
+                    "description":"builds a native executable from specified source files",
+                    "options" : {
+                        "target": {
+                            "short": "-t",
+                            "long":"--target",
+                            "description":"Specifies target binary output type. EXE or DLL",
+                            "parameter":"TARGET"
+                        },
+                        "target2": {
+                            "short": "-a",
+                            "long":"--another",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra",
+                            "parameter":"ANOTHER"
+                        },
+                        "noshort": {
+                            "long":"--noshort",
+                            "description":"Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt",
+                            "parameter":"NOSHORT"
+                        },
+                        "nolong": {
+                            "short": "-n",
+                            "description":"Specifies nolong Nunc feugiat mi a tellus consequat ",
+                            "parameter":"NOLONG"
+                        },
+                        "noparam": {
+                            "short": "-p",
+                            "long":"--noparam",
+                            "description":"Specifies noparam metus vitae pharetra auctor, sem"
+                        },
+                        "noParamNoShort": {
+                            "long":"--noshortparam",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra"
+                        },
+                        "noParamNoLong": {
+                            "short": "-l",
+                            "description":"Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet"
+                        }
+                    }
+                }
+            }
+        
+        }
+    }
+}

+ 99 - 0
src/Installers/Debian/test/test_assets/test_package_layout/docs/testdocs.1

@@ -0,0 +1,99 @@
+.TH tool1 1   
+.P 
+.SH NAME
+.P 
+.B tool1 - A tool to Vestibulum lacinia arcu eget nulla.
+.P 
+.SH SYNOPSIS
+.P 
+.B tool1 noOptionsCommand 
+.RI  " "
+.I "argument list stuff"
+.P 
+.B tool1 noArgumentListFullOptions 
+.RI  [  "-t"  |  "--target"  " "  "TARGET"  ] " " [  "--noshortparam"  ] " " [  "-l"  ] " " [  "-p"  |  "--noparam"  ] " " [  "-n"  " "  "NOLONG"  ] " " [  "--noshort"  " "  "NOSHORT"  ] " " [  "-a"  |  "--another"  " "  "ANOTHER"  ]  " "
+.I ""
+.P 
+.B tool1 fullcommand1 
+.RI  [  "-t"  |  "--target"  " "  "TARGET"  ] " " [  "--noshortparam"  ] " " [  "-l"  ] " " [  "-p"  |  "--noparam"  ] " " [  "-n"  " "  "NOLONG"  ] " " [  "--noshort"  " "  "NOSHORT"  ] " " [  "-a"  |  "--another"  " "  "ANOTHER"  ]  " "
+.I "argument list stuff"
+.P 
+.SH DESCRIPTION
+.P 
+.PP Tool1 is a really great Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. 
+.P 
+.B tool1 noOptionsCommand
+builds a native executable from specified source files
+.P 
+.B tool1 noArgumentListFullOptions
+builds a native executable from specified source files
+.P 
+.B tool1 fullcommand1
+builds a native executable from specified source files
+.P 
+.SH OPTIONS
+.P 
+.TP
+.B  "-t"  ", "  "--target"  " "  "TARGET" 
+Specifies target binary output type. EXE or DLL
+.P 
+.TP
+.B  "--noshortparam" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.TP
+.B  "-l" 
+Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet
+.P 
+.TP
+.B  "-p"  ", "  "--noparam" 
+Specifies noparam metus vitae pharetra auctor, sem
+.P 
+.TP
+.B  "-n"  " "  "NOLONG" 
+Specifies nolong Nunc feugiat mi a tellus consequat 
+.P 
+.TP
+.B  "--noshort"  " "  "NOSHORT" 
+Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt
+.P 
+.TP
+.B  "-a"  ", "  "--another"  " "  "ANOTHER" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.TP
+.B  "-t"  ", "  "--target"  " "  "TARGET" 
+Specifies target binary output type. EXE or DLL
+.P 
+.TP
+.B  "--noshortparam" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.TP
+.B  "-l" 
+Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet
+.P 
+.TP
+.B  "-p"  ", "  "--noparam" 
+Specifies noparam metus vitae pharetra auctor, sem
+.P 
+.TP
+.B  "-n"  " "  "NOLONG" 
+Specifies nolong Nunc feugiat mi a tellus consequat 
+.P 
+.TP
+.B  "--noshort"  " "  "NOSHORT" 
+Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt
+.P 
+.TP
+.B  "-a"  ", "  "--another"  " "  "ANOTHER" 
+Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra
+.P 
+.SH AUTHOR
+.P 
+.B "Test Author" " " 
+.RI ( "[email protected]" )
+.P 
+.SH COPYRIGHT
+.P 
+.B "This is the copyright of tool1."

+ 17 - 0
src/Installers/Debian/test/test_assets/test_package_layout/package_root/path_relative_to_package_root/test_exe.sh

@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# Resolve symlinks until we have the parent dir of the actual file
+SOURCE="${BASH_SOURCE[0]}"
+while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
+  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+  SOURCE="$(readlink "$SOURCE")"
+  [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
+done
+DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+
+# This is how tools should be called
+exec bash $DIR/../test_called.sh

+ 7 - 0
src/Installers/Debian/test/test_assets/test_package_layout/package_root/test_called.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+echo "script called"

+ 12 - 0
src/Installers/Debian/test/test_assets/test_package_layout/samples/testsample.cs

@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+public class Program {
+
+    public static void Main(){
+        System.Console.WriteLine("Hello World");
+    }
+
+}

+ 108 - 0
src/Installers/Debian/test/test_assets/testdocs.json

@@ -0,0 +1,108 @@
+{
+    "tools": {
+        "tool1": { 
+            "copyright": "This is the copyright of tool1.",
+            "license" : "This is the license of tool1",
+            "author":"Test Author",
+            "author_email":"[email protected]",
+            "short_description":"A tool to Vestibulum lacinia arcu eget nulla.",
+            "long_description":"Tool1 is a really great Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. ",
+            "commands": {     
+                "fullcommand1":{
+                    "description":"builds a native executable from specified source files",
+                    "options" : {
+                        "target": {
+                            "short": "-t",
+                            "long":"--target",
+                            "description":"Specifies target binary output type. EXE or DLL",
+                            "parameter":"TARGET"
+                        },
+                        "target2": {
+                            "short": "-a",
+                            "long":"--another",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra",
+                            "parameter":"ANOTHER"
+                        },
+                        "noshort": {
+                            "long":"--noshort",
+                            "description":"Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt",
+                            "parameter":"NOSHORT"
+                        },
+                        "nolong": {
+                            "short": "-n",
+                            "description":"Specifies nolong Nunc feugiat mi a tellus consequat ",
+                            "parameter":"NOLONG"
+                        },
+                        "noparam": {
+                            "short": "-p",
+                            "long":"--noparam",
+                            "description":"Specifies noparam metus vitae pharetra auctor, sem"
+                        },
+                        "noParamNoShort": {
+                            "long":"--noshortparam",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra"
+                        },
+                        "noParamNoLong": {
+                            "short": "-l",
+                            "description":"Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet"
+                        }
+                    },
+                    "argumentlist" : {
+                        "name":"argument list stuff",
+                        "description":"ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;"
+                    }
+                    
+                },
+                "noOptionsCommand":{
+                    "description":"builds a native executable from specified source files",
+                    "argumentlist" : {
+                        "name":"argument list stuff",
+                        "description":"ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;"
+                    }
+                    
+                },
+                "noArgumentListFullOptions":{
+                    "description":"builds a native executable from specified source files",
+                    "options" : {
+                        "target": {
+                            "short": "-t",
+                            "long":"--target",
+                            "description":"Specifies target binary output type. EXE or DLL",
+                            "parameter":"TARGET"
+                        },
+                        "target2": {
+                            "short": "-a",
+                            "long":"--another",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra",
+                            "parameter":"ANOTHER"
+                        },
+                        "noshort": {
+                            "long":"--noshort",
+                            "description":"Specifies noshortNam nec ante. Sed lacinia, urna non tincidunt",
+                            "parameter":"NOSHORT"
+                        },
+                        "nolong": {
+                            "short": "-n",
+                            "description":"Specifies nolong Nunc feugiat mi a tellus consequat ",
+                            "parameter":"NOLONG"
+                        },
+                        "noparam": {
+                            "short": "-p",
+                            "long":"--noparam",
+                            "description":"Specifies noparam metus vitae pharetra auctor, sem"
+                        },
+                        "noParamNoShort": {
+                            "long":"--noshortparam",
+                            "description":"Specifies another aptent taciti sociosqu ad litora torquent per conubia nostra"
+                        },
+                        "noParamNoLong": {
+                            "short": "-l",
+                            "description":"Specifies noparam nolong congue elementum. Morbi in ipsum sit amet pede facilisis laoreet"
+                        }
+                    }
+                }
+            }
+        
+        }
+    }
+}

+ 279 - 0
src/Installers/Debian/test/unit_tests/test_debian_build_lib.bats

@@ -0,0 +1,279 @@
+#!/usr/bin/env bats
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# Tests for debian_build_lib.sh
+
+setup(){
+    DIR="$BATS_TEST_DIRNAME"
+    PACKAGIFY_DIR="$(readlink -f $DIR/../../)"
+
+    PACKAGE_DIR="$BATS_TMPDIR/test-package"
+    PACKAGE_SOURCE_DIR="$BATS_TMPDIR/test-source-package"
+    INSTALL_ROOT="test-install-root"
+
+    # # Create Mock Package Directory
+    mkdir -p $PACKAGE_SOURCE_DIR/debian
+    mkdir $PACKAGE_DIR
+
+    source $PACKAGIFY_DIR/scripts/debian_build_lib.sh
+
+}
+
+teardown(){
+    # Remove Mock Package Directory
+    rm -r $PACKAGE_DIR
+    rm -r $PACKAGE_SOURCE_DIR
+}
+
+@test "add_system_file_placement populates placement array" {
+    
+    add_system_file_placement "testfile0" "testdir0"
+    add_system_file_placement "testfile1" "testdir1"
+
+    [ $placement_index -eq "2" ]
+    [ "${install_placement[0]}" = "testfile0 testdir0" ]
+    [ "${install_placement[1]}" = "testfile1 testdir1" ]
+}
+
+@test "add_system_dir_placement adds files in dir to placement array" {
+    test_package_rel_dir="test-dir"
+    abs_test_path="/abs/test/path"
+
+    mkdir $PACKAGE_SOURCE_DIR/$test_package_rel_dir
+    echo "file0 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/file0
+    echo "file1 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/file1
+    echo "file2 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/file2
+
+    add_system_dir_placement $test_package_rel_dir $abs_test_path
+
+    rm -r $PACKAGE_SOURCE_DIR/$test_package_rel_dir
+
+    [ "$placement_index" -eq 3 ]
+    [ "${install_placement[0]}" = "$test_package_rel_dir/file0 $abs_test_path" ]
+    [ "${install_placement[1]}" = "$test_package_rel_dir/file1 $abs_test_path" ]
+    [ "${install_placement[2]}" = "$test_package_rel_dir/file2 $abs_test_path" ]
+}
+
+@test "add_system_dir_placement adds all files in subdir to placement array" {
+    test_package_rel_dir="test-dir"
+    test_package_rel_subdir="test-subdir"
+    abs_test_path="/abs/test/path"
+
+    mkdir -p $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir
+    echo "file0 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir/file0
+    echo "file1 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir/file1
+    echo "file2 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir/file2
+
+    add_system_dir_placement $test_package_rel_dir $abs_test_path
+
+    rm -r $PACKAGE_SOURCE_DIR/$test_package_rel_dir
+
+    [ "$placement_index" -eq "3" ]
+    [ "${install_placement[0]}" = "$test_package_rel_dir/$test_package_rel_subdir/file0 $abs_test_path/$test_package_rel_subdir" ]
+    [ "${install_placement[1]}" = "$test_package_rel_dir/$test_package_rel_subdir/file1 $abs_test_path/$test_package_rel_subdir" ]
+    [ "${install_placement[2]}" = "$test_package_rel_dir/$test_package_rel_subdir/file2 $abs_test_path/$test_package_rel_subdir" ]
+}
+
+@test "add_system_dir_placement adds all files in subdir and dir to placement array" {
+    test_package_rel_dir="test-dir"
+    test_package_rel_subdir="test-subdir"
+    abs_test_path="/abs/test/path"
+
+    mkdir -p $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir
+    echo "file0 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/file0
+    echo "file1 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/file1
+    echo "file2 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/file2
+    echo "file3 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir/file3
+    echo "file4 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir/file4
+    echo "file5 contents" > $PACKAGE_SOURCE_DIR/$test_package_rel_dir/$test_package_rel_subdir/file5
+
+    add_system_dir_placement $test_package_rel_dir $abs_test_path
+
+    rm -r $PACKAGE_SOURCE_DIR/$test_package_rel_dir
+
+    [ "$placement_index" -eq "6" ]
+    [ "${install_placement[0]}" = "$test_package_rel_dir/file0 $abs_test_path" ]
+    [ "${install_placement[1]}" = "$test_package_rel_dir/file1 $abs_test_path" ]
+    [ "${install_placement[2]}" = "$test_package_rel_dir/file2 $abs_test_path" ]
+    [ "${install_placement[3]}" = "$test_package_rel_dir/$test_package_rel_subdir/file3 $abs_test_path/$test_package_rel_subdir" ]
+    [ "${install_placement[4]}" = "$test_package_rel_dir/$test_package_rel_subdir/file4 $abs_test_path/$test_package_rel_subdir" ]
+    [ "${install_placement[5]}" = "$test_package_rel_dir/$test_package_rel_subdir/file5 $abs_test_path/$test_package_rel_subdir" ]
+}
+
+@test "write_debian_install_file writes to debian/install" {
+    add_system_file_placement "somefile" "/some/abs/path"
+    write_debian_install_file
+
+    [ -f "$PACKAGE_SOURCE_DIR/debian/install" ]
+    [ "$(cat $PACKAGE_SOURCE_DIR/debian/install)" = "somefile /some/abs/path" ]
+}
+
+@test "add_file_to_install adds file to package_dest_dir" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    local_subdir="the/path/should/not/matter"
+    package_test_dir="package-dir"
+
+    mkdir $PACKAGE_SOURCE_DIR/$package_test_dir
+    mkdir -p $local_test_dir/$local_subdir
+    echo "file0 contents" > $local_test_dir/$local_subdir/file0
+    echo "file1 contents" > $local_test_dir/$local_subdir/file1
+    echo "file2 contents" > $local_test_dir/$local_subdir/file2
+
+    add_file_to_install "$local_test_dir/$local_subdir/file0" "$package_test_dir"
+    add_file_to_install "$local_test_dir/$local_subdir/file1" "$package_test_dir"
+    add_file_to_install "$local_test_dir/$local_subdir/file2" "$package_test_dir"
+
+    rm -r $local_test_dir
+
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file0" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file1" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file2" ]
+}
+
+@test "add_file_to_install added files written to install file after write_debian_install_file" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    local_subdir="the/path/should/not/matter"
+    package_test_dir="package-dir"
+
+    mkdir $PACKAGE_SOURCE_DIR/$package_test_dir
+    mkdir -p $local_test_dir/$local_subdir
+    echo "file0 contents" > $local_test_dir/$local_subdir/file0
+
+    add_file_to_install "$local_test_dir/$local_subdir/file0" "$package_test_dir"
+
+    rm -r $local_test_dir
+
+    [ ! -e $PACKAGE_SOURCE_DIR/debian/install ]
+    write_debian_install_file
+    [ -f $PACKAGE_SOURCE_DIR/debian/install ]
+    [ "$(cat $PACKAGE_SOURCE_DIR/debian/install)" = "$package_test_dir/file0 $INSTALL_ROOT/$package_test_dir" ]
+}
+
+@test "add_dir_to_install adds files in dir to package_dest_dir" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    package_test_dir="package-dir"
+
+    mkdir $PACKAGE_SOURCE_DIR/$package_test_dir
+    mkdir $local_test_dir
+    echo "file0 contents" > $local_test_dir/file0
+    echo "file1 contents" > $local_test_dir/file1
+    echo "file2 contents" > $local_test_dir/file2
+
+    add_dir_to_install "$local_test_dir" "$package_test_dir"
+
+    rm -r $local_test_dir
+
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file0" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file1" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file2" ]
+}
+
+@test "add_dir_to_install adds files in subdirectory tree to package_dest_dir" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    local_test_subdir="local-subdir"
+
+    package_test_dir="package-dir"
+
+    mkdir $PACKAGE_SOURCE_DIR/$package_test_dir
+    mkdir -p $local_test_dir/$local_test_subdir
+    echo "file0 contents" > $local_test_dir/$local_test_subdir/file0
+    echo "file1 contents" > $local_test_dir/$local_test_subdir/file1
+    echo "file2 contents" > $local_test_dir/$local_test_subdir/file2
+
+    add_dir_to_install "$local_test_dir" "$package_test_dir"
+
+    rm -r $local_test_dir
+
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/$local_test_subdir/file0" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/$local_test_subdir/file1" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/$local_test_subdir/file2" ]
+}
+
+@test "add_dir_to_install adds files in directory and subdirectory tree to package_dest_dir" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    local_test_subdir="local-subdir"
+
+    package_test_dir="package-dir"
+
+    mkdir $PACKAGE_SOURCE_DIR/$package_test_dir
+    mkdir -p $local_test_dir/$local_test_subdir
+
+    echo "file0 contents" > $local_test_dir/file0
+    echo "file1 contents" > $local_test_dir/file1
+    echo "file2 contents" > $local_test_dir/file2
+    echo "file3 contents" > $local_test_dir/$local_test_subdir/file3
+    echo "file4 contents" > $local_test_dir/$local_test_subdir/file4
+    echo "file5 contents" > $local_test_dir/$local_test_subdir/file5
+
+    add_dir_to_install "$local_test_dir" "$package_test_dir"
+
+    rm -r $local_test_dir
+
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file0" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file1" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/file2" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/$local_test_subdir/file3" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/$local_test_subdir/file4" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$package_test_dir/$local_test_subdir/file5" ]
+}
+
+@test "add_dir_to_install added files written to install file after write_debian_install_file" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    package_test_dir="package-dir"
+
+    mkdir $PACKAGE_SOURCE_DIR/$package_test_dir
+    mkdir -p $local_test_dir
+
+    echo "file0 contents" > $local_test_dir/file0
+
+    add_dir_to_install "$local_test_dir" "$package_test_dir"
+
+    rm -r $local_test_dir
+
+    [ ! -e $PACKAGE_SOURCE_DIR/debian/install ]
+    write_debian_install_file
+    [ -f $PACKAGE_SOURCE_DIR/debian/install ]
+    [ "$(cat $PACKAGE_SOURCE_DIR/debian/install)" = "$package_test_dir/file0 $INSTALL_ROOT/$package_test_dir" ]
+}
+
+@test "add_dir_to_install with empty dest dir outputs to PACKAGE_SOURCE_DIR" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    mkdir -p $local_test_dir
+
+    echo "file0 contents" > $local_test_dir/file0
+
+    add_dir_to_install "$local_test_dir" ""
+
+    rm -r $local_test_dir
+
+    [ -f "$PACKAGE_SOURCE_DIR/file0" ]
+}
+
+
+@test "add_dir_to_install with empty dest dir adds files in directory and subdirectory tree to $PACKAGE_SOURCE_DIR" {
+    local_test_dir="$BATS_TMPDIR/local-dir"
+    local_test_subdir="local-subdir"
+
+    mkdir -p $local_test_dir/$local_test_subdir
+
+    echo "file0 contents" > $local_test_dir/file0
+    echo "file1 contents" > $local_test_dir/file1
+    echo "file2 contents" > $local_test_dir/file2
+    echo "file3 contents" > $local_test_dir/$local_test_subdir/file3
+    echo "file4 contents" > $local_test_dir/$local_test_subdir/file4
+    echo "file5 contents" > $local_test_dir/$local_test_subdir/file5
+
+    add_dir_to_install "$local_test_dir" ""
+
+    rm -r $local_test_dir
+
+    [ -f "$PACKAGE_SOURCE_DIR/file0" ]
+    [ -f "$PACKAGE_SOURCE_DIR/file1" ]
+    [ -f "$PACKAGE_SOURCE_DIR/file2" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$local_test_subdir/file3" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$local_test_subdir/file4" ]
+    [ -f "$PACKAGE_SOURCE_DIR/$local_test_subdir/file5" ]
+}

+ 28 - 0
src/Installers/Debian/test/unit_tests/test_scripts.bats

@@ -0,0 +1,28 @@
+#!/usr/bin/env bats
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# Tests for debian_build_lib.sh
+
+setup(){
+    DIR="$BATS_TEST_DIRNAME"
+    PACKAGIFY_DIR="$(readlink -f $DIR/../../)"
+}
+
+@test "manpage generation is identical to lkg file" {
+    # Output is file "tool1.1"
+    # LKG file is "lkgtestman.1"
+    python $PACKAGIFY_DIR/scripts/manpage_generator.py $PACKAGIFY_DIR/test/test_assets/testdocs.json $PACKAGIFY_DIR/test/test_assets
+
+    # Test Output existence
+    [ -f $PACKAGIFY_DIR/test/test_assets/tool1.1 ]
+    
+    # Test Output matches LKG
+    # If this is failing double check line ending style
+    [ -z "$(diff "$PACKAGIFY_DIR/test/test_assets/tool1.1" "$PACKAGIFY_DIR/test/test_assets/lkgtestman.1")" ]
+
+    # Cleanup
+    rm $PACKAGIFY_DIR/test/test_assets/tool1.1
+}