Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate bdist ZIP members via Meson #199

Merged
merged 8 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ jobs:
with:
repository: ${{ inputs.openslide_repo }}
ref: ${{ inputs.openslide_ref }}
# make sure "git describe" works
fetch-depth: 0
path: override/openslide
persist-credentials: false
- name: Check out OpenSlide Java
Expand All @@ -95,6 +97,8 @@ jobs:
with:
repository: ${{ inputs.openslide_java_repo }}
ref: ${{ inputs.openslide_java_ref }}
# make sure "git describe" works
fetch-depth: 0
path: override/openslide_java
persist-credentials: false
- name: Collect overrides
Expand All @@ -119,8 +123,8 @@ jobs:
run: |
artifact="openslide-windows-${{ inputs.pkgver }}"
echo "artifact=$artifact" >> $GITHUB_OUTPUT
mkdir -p "artifacts/$artifact"
mv "openslide-winbuild-${{ inputs.pkgver }}.zip" "artifacts/$artifact"
mkdir -p "output/$artifact"
mv "openslide-winbuild-${{ inputs.pkgver }}.zip" "output/$artifact"
if [ -d override/openslide ]; then
suffix=$(git -C override/openslide rev-parse HEAD | cut -c-7)
echo "version_suffix=$suffix" >> $GITHUB_OUTPUT
Expand All @@ -129,7 +133,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: ${{ steps.prep.outputs.artifact }}
path: artifacts
path: output

windows:
name: Windows
Expand Down Expand Up @@ -164,14 +168,14 @@ jobs:
fi
./build.sh ${suffix:+-s$suffix} -p "${{ inputs.pkgver }}" \
$werror bdist
mkdir -p "artifacts/${{ needs.sdist.outputs.artifact }}"
mkdir -p "output/${{ needs.sdist.outputs.artifact }}"
mv "openslide-win64-${{ inputs.pkgver }}.zip" \
"artifacts/${{ needs.sdist.outputs.artifact }}"
"output/${{ needs.sdist.outputs.artifact }}"
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ needs.sdist.outputs.artifact }}
path: artifacts
path: output

windows-smoke:
name: Windows smoke test
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
override
openslide-win*

__pycache__

# ignore package cache and unpacked packages
/subprojects/*
!/subprojects/*.wrap
Expand Down
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ repos:

- repo: meta
hooks:
# - id: check-hooks-apply
- id: check-hooks-apply
- id: check-useless-excludes

- repo: local
Expand All @@ -69,3 +69,9 @@ repos:
# don't work because --multiline causes them to match at newlines.
entry: "(?<!.)(?!.)|\nfrom __future__ import annotations"
args: [--multiline, --negate]
- id: argparse
name: Require common.argparse wrapper
language: pygrep
exclude: common/argparse.py
types: [python]
entry: "(add_argument|parse_args)\\("
25 changes: 25 additions & 0 deletions artifacts/get-introspect-command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
#
# Tools for building OpenSlide and its dependencies
#
# Copyright (c) 2023 Benjamin Gilbert
# All rights reserved.
#
# This script is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License, version 2.1,
# as published by the Free Software Foundation.
#
# This script is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this script. If not, see <http://www.gnu.org/licenses/>.
#

from __future__ import annotations

import os

print(os.environ['MESONINTROSPECT'])
90 changes: 90 additions & 0 deletions artifacts/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
env = environment()
env.prepend('PYTHONPATH', meson.project_source_root())
# Meson doesn't pass MESONINTROSPECT to custom targets
# https://github.com/mesonbuild/meson/issues/12492
mesonintrospect = run_command(
files('get-introspect-command.py'),
capture : true,
check : true,
).stdout().strip()
env.set('MESONINTROSPECT', mesonintrospect)
# nor MESON_SOURCE_ROOT
env.set('MESON_SOURCE_ROOT', meson.project_source_root())
env.set('LD', find_program('ld').full_path())
if system == 'linux'
env.set('PATCHELF', find_program('patchelf').full_path())
endif
if system == 'darwin'
env.set('DSYMUTIL', find_program('dsymutil').full_path())
env.set('DYLD_INFO', find_program('dyld_info').full_path())
env.set('INSTALL_NAME_TOOL', find_program('install_name_tool').full_path())
env.set('OTOOL', find_program('otool').full_path())
env.set('STRIP', find_program('strip').full_path())
else
env.set('OBJCOPY', find_program('objcopy').full_path())
env.set('OBJDUMP', find_program('objdump').full_path())
endif

fs = import('fs')
openslide_jar = openslide_java.get_variable('openslide_jar')
artifact_dir = get_option('prefix') / 'artifacts'
artifacts = [
custom_target(
command : [find_program('write-project-versions.py'), '-o', '@OUTPUT@'],
output : 'VERSIONS.md',
# ensure we regenerate after dependency updates
build_always_stale : true,
env : env,
install : true,
install_dir : get_option('datadir'),
),
custom_target(
command : ['cp', '@INPUT@', '@OUTPUT@'],
input : openslide_jar,
output : fs.name(openslide_jar.full_path()),
install : true,
install_dir : artifact_dir,
),
]

licenses = custom_target(
command : [find_program('write-licenses.py'), '@OUTPUT@'],
output : 'licenses',
# ensure we regenerate after dependency updates
build_always_stale : true,
env : env,
install : true,
install_dir : get_option('datadir'),
)
artifacts += licenses

libopenslide = openslide.get_variable('libopenslide')
if system == 'windows'
artifacts += custom_target(
command : [
find_program('write-import-library.py'), '@INPUT@', '@OUTPUT@'
],
input : libopenslide,
output : 'libopenslide.lib',
env : env,
install : true,
install_dir : artifact_dir,
)
endif

postprocess = find_program('postprocess-binary.py')
foreach bin : [
libopenslide,
openslide.get_variable('slidetool'),
openslide_java.get_variable('openslide_jni'),
]
name = fs.name(bin.full_path())
artifacts += custom_target(
command : [postprocess, '@INPUT@', '-o', '@OUTPUT0@', '-d', '@OUTPUT1@'],
input : bin,
output : [name, name + (system == 'darwin' ? '.dSYM' : '.debug')],
env : env,
install : true,
install_dir : artifact_dir,
)
endforeach
156 changes: 156 additions & 0 deletions artifacts/postprocess-binary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
#
# Tools for building OpenSlide and its dependencies
#
# Copyright (c) 2011-2015 Carnegie Mellon University
# Copyright (c) 2022-2023 Benjamin Gilbert
# All rights reserved.
#
# This script is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License, version 2.1,
# as published by the Free Software Foundation.
#
# This script is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this script. If not, see <http://www.gnu.org/licenses/>.
#

from __future__ import annotations

import os
from pathlib import Path
import re
import subprocess

from common.argparse import TypedArgs
from common.meson import meson_host


def library_symbols(file: Path) -> list[str]:
if host == 'linux':
out = subprocess.check_output(
[os.environ['OBJDUMP'], '-T', file]
).decode()
return [line.split()[6] for line in out.split('\n') if '.text' in line]
elif host == 'darwin':
out = subprocess.check_output(
[os.environ['DYLD_INFO'], '-exports', file]
).decode()
return [
line.split()[1].lstrip('_')
for line in out.split('\n')
if ' 0x' in line
]
elif host == 'windows':
out = subprocess.check_output(
[os.environ['OBJDUMP'], '-p', file]
).decode()
active = False
syms: list[str] = []
for line in out.split('\n'):
if active:
if not line.strip():
return syms
syms.append(line.split()[2])
elif 'Ordinal/Name Pointer' in line:
active = True
raise Exception("Couldn't parse objdump output")
else:
raise Exception(f'Unknown host: {host}')


class Args(TypedArgs):
file: Path
output: Path
debuginfo: Path


args = Args(
'postprocess-binary', description='Mangle shared library or executable.'
)
args.add_arg('-o', '--output', type=Path, required=True, help='output file')
args.add_arg(
'-d', '--debuginfo', type=Path, required=True, help='output debug symbols'
)
args.add_arg('file', type=Path, help='input file')
args.parse()
host = meson_host()

# split debuginfo
if host == 'darwin':
subprocess.check_call(
[os.environ['DSYMUTIL'], '-o', args.debuginfo, args.file]
)
subprocess.check_call(
[os.environ['STRIP'], '-u', '-r', '-o', args.output, args.file]
)
else:
objcopy = os.environ['OBJCOPY']
subprocess.check_call(
[objcopy, '--only-keep-debug', args.file, args.debuginfo]
)
os.chmod(args.debuginfo, 0o644)
# debuglink without a directory path enables search semantics
assert args.debuginfo.parent == args.output.parent
subprocess.check_call(
[
objcopy,
'-S',
f'--add-gnu-debuglink={args.debuginfo.name}',
args.file.absolute(),
args.output.absolute(),
],
cwd=args.debuginfo.parent,
)

# check for extra symbol exports
if re.search('\\.(dll|dylib|so[.0-9]*)$', args.file.name):
syms = library_symbols(args.file)
if not syms:
raise Exception(f"Couldn't find exported symbols in {args.file}")
syms = [
# filter out acceptable symbols
sym
for sym in syms
if not sym.startswith('openslide_') and sym != 'JNI_OnLoad'
]
if syms:
raise Exception(f'Unexpected exports in {args.file}: {syms}')

# update rpath
if host == 'linux' and not re.match('\\.so[.0-9]*$', args.file.name):
subprocess.check_call(
[os.environ['PATCHELF'], '--set-rpath', '$ORIGIN/../lib', args.output]
)
elif host == 'darwin' and not args.file.name.endswith('.dylib'):
out = subprocess.check_output(
[os.environ['OTOOL'], '-l', args.output]
).decode()
active = False
for line in out.split('\n'):
if 'cmd LC_RPATH' in line:
active = True
elif active:
words = line.split()
if words[0] == 'path':
old_rpath = words[1]
break
else:
raise Exception("Couldn't read LC_RPATH")
if args.file.name.endswith('.jnilib'):
new_rpath = '@loader_path'
else:
new_rpath = '@loader_path/../lib'
subprocess.check_call(
[
os.environ['INSTALL_NAME_TOOL'],
'-rpath',
old_rpath,
new_rpath,
args.output,
]
)
Loading