2023-01-12 03:52:49 +03:00
#!/usr/bin/env python3
"""
Packaging helper script for SymCrypt .
2023-07-15 04:35:49 +03:00
NOTE : For Windows targets , this script only works with MSBuild builds , because CMake cannot build
Windows kernel drivers .
2023-07-17 23:30:03 +03:00
This script reads the package configuration from SymCryptPackage . json in the repo root , which
contains a list of files in the following format :
2023-07-15 04:35:49 +03:00
{
" source " : " ... " ,
" dest " : " ... " ,
" platform " : " ... " ,
" arch " : " ... " ,
" config " : " ... "
}
where :
source : The source file path
dest : The destination file path
platform : Platforms to include the file ( " win32 " , " linux " )
arch : Architectures to include the file ( " x86 " , " amd64 " , " arm " , " arm64 " )
config : Configurations to include the file ( " debug " , " release " , " sanitize " )
Multiple platforms , architectures , and configurations can be specified by separating them with commas ,
e . g . " windows,linux " or " x86,amd64 " . If platform , arch or config are omitted , the file will be included
on all platforms , architectures , or configurations , respectively .
The following special tokens in the source and destination paths :
$ { SOURCE_DIR } : The root of the SymCrypt source tree
$ { BIN_DIR } : The build output directory
$ { MODULE_NAME } : The name of the module to package ( currently only relevant for Linux )
$ { VERSION_API } : SymCrypt API version
$ { VERSION_MINOR } : Minor version
$ { VERSION_PATCH } : Patch version
2023-01-12 03:52:49 +03:00
Copyright ( c ) Microsoft Corporation . Licensed under the MIT license .
"""
import argparse
2023-07-15 04:35:49 +03:00
import json
2023-01-12 03:52:49 +03:00
import os
import pathlib
import re
import shutil
import subprocess
import sys
import tempfile
from typing import Dict , Tuple
2023-07-15 04:35:49 +03:00
2023-05-23 11:11:33 +03:00
from version import SymCryptVersion , get_version_info
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
def get_file_list ( bin_dir : pathlib . Path , config : str , module_name : str ) - > Dict [ str , str ] :
2023-01-12 03:52:49 +03:00
"""
Replaces variables in the package file map with their actual values .
bin_dir : The build output directory .
module_name : The name of the module being packaged .
config : The build configuration ( Debug / Release / Sanitize )
"""
source_dir = pathlib . Path ( __file__ ) . parent . parent . resolve ( )
2023-05-23 11:11:33 +03:00
version = get_version_info ( )
2023-01-12 03:52:49 +03:00
replacement_map = {
" $ {SOURCE_DIR} " : str ( source_dir ) ,
" $ {BIN_DIR} " : str ( bin_dir . resolve ( ) ) ,
" $ {MODULE_NAME} " : module_name ,
2023-05-23 11:11:33 +03:00
" $ {VERSION_API} " : str ( version . major ) ,
" $ {VERSION_MINOR} " : str ( version . minor ) ,
" $ {VERSION_PATCH} " : str ( version . patch )
2023-01-12 03:52:49 +03:00
}
2023-07-15 04:35:49 +03:00
file_list = [ ]
2023-07-17 23:30:03 +03:00
with open ( source_dir / " SymCryptPackage.json " ) as f :
2023-07-15 04:35:49 +03:00
file_json = json . load ( f )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
for file in file_json :
for replacement_key , replacement_value in replacement_map . items ( ) :
file [ " source " ] = file [ " source " ] . replace ( replacement_key , replacement_value )
file [ " dest " ] = file [ " dest " ] . replace ( replacement_key , replacement_value )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
file_list . append ( file )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
return file_list
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
def prepare_package ( build_dir : pathlib . Path , package_dir : pathlib . Path ,
arch : str , config : str , module_name : str ) - > None :
2023-01-12 03:52:49 +03:00
"""
2023-07-15 04:35:49 +03:00
Prepares the files for packaging by copying them into a temporary directory . Does not create the archive .
2023-01-12 03:52:49 +03:00
build_dir : The build output directory .
2023-07-15 04:35:49 +03:00
package_dir : The directory to copy the files to . Must be an existing directory .
2023-01-12 03:52:49 +03:00
arch : Architecture of the binaries to package ( for inclusion in the package name ) .
config : The build configuration ( Debug / Release / Sanitize ) .
module_name : The name of the module to package .
"""
2023-07-15 04:35:49 +03:00
file_list = get_file_list ( build_dir , config , module_name )
for file in file_list :
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
target_platform = file . get ( " platform " )
target_arch = file . get ( " arch " )
target_config = file . get ( " config " )
2023-05-23 11:11:33 +03:00
2023-07-15 04:35:49 +03:00
if target_platform is not None and sys . platform not in target_platform . split ( " , " ) :
continue
2023-05-23 11:11:33 +03:00
2023-07-15 04:35:49 +03:00
if target_arch is not None and arch not in target_arch . split ( " , " ) :
continue
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
if target_config is not None and config not in target_config . split ( " , " ) :
continue
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
print ( " Copying " + file [ " dest " ] )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
source = pathlib . Path ( file [ " source " ] )
destination = pathlib . Path ( file [ " dest " ] )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
if not source . exists ( ) :
if source . suffix == " .sys " and arch == " X86 " :
# Ignore missing drivers on x86, since we don't build them for that arch
print ( " Skipping driver " + str ( source ) + " for x86 package " )
continue
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
raise Exception ( " Source file " + str ( source ) + " does not exist. " )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
destination = package_dir / destination
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
if not destination . parent . exists ( ) :
destination . parent . mkdir ( parents = True )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
# Do not follow symlinks, as we want to preserve relative symlinks in the package
# e.g. libsymcrypt.so -> libsymcrypt.so.x
shutil . copy ( source , destination , follow_symlinks = False )
def create_archive ( package_dir : pathlib . Path , release_dir : pathlib . Path ,
arch : str , config : str , module_name : str ) - > None :
"""
Creates an archive of the package by compressing the files in the package directory into a zip
or tar . gz archive , depending on the platform .
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
package_dir : The directory to place the package files in . Must be an existing directory .
release_dir : The directory to place the archive in . Must be an existing directory .
arch : Architecture of the binaries to package ( for inclusion in the package name ) .
config : The build configuration ( Debug / Release / Sanitize ) .
module_name : The name of the module to package .
"""
2023-05-23 11:11:33 +03:00
2023-07-15 04:35:49 +03:00
version = get_version_info ( )
archive_name = " symcrypt- {} - {} - {} - {} - {} . {} . {} - {} " . format (
sys . platform ,
module_name ,
arch ,
config ,
str ( version . major ) ,
str ( version . minor ) ,
str ( version . patch ) ,
version . commit_hash
)
archive_type = None
archive_ext = None
if sys . platform == " linux " :
archive_type = " gztar "
archive_ext = " .tar.gz "
elif sys . platform == " win32 " :
archive_type = " zip "
archive_ext = " .zip "
else :
raise Exception ( " Unsupported platform: " + sys . platform )
cwd = os . getcwd ( )
try :
os . chdir ( release_dir )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
if os . path . exists ( archive_name + archive_ext ) :
raise Exception ( " Archive " + archive_name + archive_ext + " already exists. " )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
print ( " Creating archive " + archive_name + " in " + str ( release_dir . resolve ( ) ) + " ... " )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
shutil . make_archive ( archive_name , archive_type , package_dir , owner = " root " , group = " root " )
2023-01-12 03:52:49 +03:00
2023-07-15 04:35:49 +03:00
print ( " Done. " )
finally :
os . chdir ( cwd )
2023-01-12 03:52:49 +03:00
def main ( ) - > None :
"""
Entrypoint
"""
parser = argparse . ArgumentParser ( description = " Packaging helper script for SymCrypt. " )
parser . add_argument ( " build_dir " , type = pathlib . Path , help = " Build output directory. " )
2023-07-15 04:35:49 +03:00
parser . add_argument ( " arch " , type = str . lower , help = " Architecture of the binaries to package (for inclusion in the package name). " , choices = ( " x86 " , " amd64 " , " arm64 " ) )
2023-01-12 03:52:49 +03:00
parser . add_argument ( " config " , type = str , help = " Build configuration. " , choices = [ " Debug " , " Release " , " Sanitize " ] )
parser . add_argument ( " module_name " , type = str , help = " Name of the module to package. " )
parser . add_argument ( " release_dir " , type = pathlib . Path , help = " Directory to place the release in. " )
2023-07-15 04:35:49 +03:00
parser . add_argument ( " --no-archive " , action = " store_true " , help = " Do not create a compressed archive, just copy the files. " , default = False )
2023-01-12 03:52:49 +03:00
args = parser . parse_args ( )
2023-07-15 04:35:49 +03:00
# Try to create the release directory first to check for permissions
if args . no_archive and args . release_dir . exists ( ) :
print ( " Directory " + str ( args . release_dir ) + " already exists; please remove it first. " )
exit ( - 1 )
args . release_dir . mkdir ( parents = True , exist_ok = True )
with tempfile . TemporaryDirectory ( ) as temp_dir :
temp_dir = pathlib . Path ( temp_dir )
prepare_package ( args . build_dir , temp_dir , args . arch , args . config , args . module_name )
if args . no_archive :
print ( " Copying tree to " + str ( args . release_dir . resolve ( ) ) + " ... " )
shutil . copytree ( temp_dir , args . release_dir , symlinks = True , dirs_exist_ok = True )
print ( " Done. " )
else :
create_archive ( temp_dir , args . release_dir , args . arch , args . config , args . module_name )
2023-01-12 03:52:49 +03:00
if __name__ == " __main__ " :
main ( )