Skip to content
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
6 changes: 3 additions & 3 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,21 @@ jobs:
- os: ubuntu-latest
TARGET: ubuntu
CMD_BUILD: >
pyinstaller --onefile cli.py -n cli --additional-hooks-dir=hooks --hidden-import=pkg_resources.extern --add-binary "LICENSE:LICENSES" --add-binary "LICENSES/LicenseRef-3rd_party_licenses.txt:LICENSES" &&
pyinstaller cli.spec && \
mv dist/cli fosslight_bin_ubuntu
OUT_FILE_NAME: fosslight_bin_ubuntu
ASSET_MIME: application/octet-stream
- os: macos-latest
TARGET: macos
CMD_BUILD: >
pyinstaller --onefile cli.py -n cli --additional-hooks-dir=hooks --hidden-import=pkg_resources.extern --add-binary "LICENSE:LICENSES" --add-binary "LICENSES/LicenseRef-3rd_party_licenses.txt:LICENSES" &&
pyinstaller cli.spec && \
mv dist/cli fosslight_bin_macos
OUT_FILE_NAME: fosslight_bin_macos
ASSET_MIME: aapplication/x-mach-binary
- os: windows-latest
TARGET: windows
CMD_BUILD: >
pyinstaller --onefile cli.py -n cli --additional-hooks-dir=hooks --hidden-import=pkg_resources.extern --add-binary "LICENSE;LICENSES" --add-binary "LICENSES\LicenseRef-3rd_party_licenses.txt;LICENSES" &&
pyinstaller cli.spec && \
move dist/cli.exe fosslight_bin_windows.exe
OUT_FILE_NAME: fosslight_bin_windows.exe
ASSET_MIME: application/vnd.microsoft.portable-executable
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
python -m pip install --upgrade pip
pip install .
pip install pyinstaller
pyinstaller --onefile cli.py -n cli --additional-hooks-dir=hooks --hidden-import=pkg_resources.extern &&
pyinstaller cli.spec
move dist\cli.exe tests\fosslight_bin_windows.exe
.\tests\fosslight_bin_windows.exe

Expand Down
8 changes: 8 additions & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,11 @@ License: Apache-2.0
Files: .bumpversion.cfg
Copyright: 2021 LG Electronics
License: Apache-2.0

Files: third_party/*
Copyright: 2013 Jeremy Long
License: Apache-2.0

Files: cli.spec
Copyright: 2025 LG Electronics
License: Apache-2.0
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include LICENSE
include README.md
include requirements.txt
recursive-include third_party/dependency-check *
51 changes: 51 additions & 0 deletions cli.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
['cli.py'],
pathex=['src'], # source package path for fosslight_binary
binaries=[],
datas=[
# Use original repository location (no pre-install): third_party/dependency-check
('third_party/dependency-check/bin', 'third_party/dependency-check/bin'),
('third_party/dependency-check/lib', 'third_party/dependency-check/lib'),
('third_party/dependency-check/licenses', 'third_party/dependency-check/licenses'),
('third_party/dependency-check', 'third_party/dependency-check'), # txt/md root files
('LICENSES', 'LICENSES'),
('LICENSE', 'LICENSES'),
],
hiddenimports=[
'pkg_resources.extern',
'fosslight_binary.cli',
'fosslight_binary._jar_analysis',
'fosslight_binary._binary',
],
hookspath=['hooks'],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='cli',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
28 changes: 27 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,27 @@
install_requires = f.read().splitlines()

_PACKAEG_NAME = 'fosslight_binary'
_TEMP_DC_DIR = os.path.join('src', _PACKAEG_NAME, 'third_party', 'dependency-check')
_LICENSE_FILE = 'LICENSE'
_LICENSE_DIR = 'LICENSES'

if __name__ == "__main__":
dest_path = os.path.join('src', _PACKAEG_NAME, _LICENSE_DIR)
# Temporarily copy dependency-check bundle inside package for wheel build
try:
if os.path.isdir('third_party/dependency-check'):
if os.path.exists(_TEMP_DC_DIR):
shutil.rmtree(_TEMP_DC_DIR)
os.makedirs(_TEMP_DC_DIR, exist_ok=True)
# Copy tree (shallow manual to preserve permissions)
for root, dirs, files in os.walk('third_party/dependency-check'):
rel_root = os.path.relpath(root, 'third_party/dependency-check')
target_root = os.path.join(_TEMP_DC_DIR, rel_root) if rel_root != '.' else _TEMP_DC_DIR
os.makedirs(target_root, exist_ok=True)
for f in files:
shutil.copy2(os.path.join(root, f), os.path.join(target_root, f))
except Exception as e:
print(f'Warning: Fail to stage dependency-check bundle: {e}')
try:
if not os.path.exists(dest_path):
os.mkdir(dest_path)
Expand Down Expand Up @@ -61,7 +77,16 @@
'python-magic'
],
},
package_data={_PACKAEG_NAME: [os.path.join(_LICENSE_DIR, '*')]},
package_data={
_PACKAEG_NAME: [
os.path.join(_LICENSE_DIR, '*'),
'third_party/dependency-check/bin/*',
'third_party/dependency-check/lib/*',
'third_party/dependency-check/licenses/*',
'third_party/dependency-check/*.txt',
'third_party/dependency-check/*.md',
]
},
include_package_data=True,
entry_points={
"console_scripts": [
Expand All @@ -72,3 +97,4 @@
}
)
shutil.rmtree(dest_path, ignore_errors=True)
shutil.rmtree(_TEMP_DC_DIR, ignore_errors=True)
196 changes: 63 additions & 133 deletions src/fosslight_binary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,146 +4,76 @@
# SPDX-License-Identifier: Apache-2.0
import logging
import os
import stat
import subprocess
import tempfile
import urllib.request
import zipfile
import sys

logger = logging.getLogger(__name__)
DEPENDENCY_CHECK_VERSION = "12.1.7"


def _install_dependency_check():
"""Install OWASP dependency-check"""
try:
# Skip if explicitly disabled
if os.environ.get('FOSSLIGHT_SKIP_AUTO_INSTALL', '').lower() in ('1', 'true', 'yes'):
logger.info("Auto-install disabled by environment variable")
return

env_home = os.environ.get('DEPENDENCY_CHECK_HOME', '').strip()
install_dir = None
forced_env = False
if env_home:
# Normalize
env_home_abs = os.path.abspath(env_home)
# Detect if env_home already the actual extracted root (ends with dependency-check)
candidate_bin_win = os.path.join(env_home_abs, 'bin', 'dependency-check.bat')
candidate_bin_nix = os.path.join(env_home_abs, 'bin', 'dependency-check.sh')
if os.path.exists(candidate_bin_win) or os.path.exists(candidate_bin_nix):
# env points directly to dependency-check root; install_dir is its parent
install_dir = os.path.dirname(env_home_abs)
forced_env = True
else:
# Assume env_home is the base directory where we should extract dependency-check/
install_dir = env_home_abs

if not install_dir:
# Fallback hierarchy: executable dir (if frozen) -> CWD
candidate_base = None
if getattr(sys, 'frozen', False):
exe_dir = os.path.dirname(os.path.abspath(sys.executable))
candidate_base = os.path.join(exe_dir, 'fosslight_dc_bin')

if not os.access(exe_dir, os.W_OK):
candidate_base = None
else:
logger.debug(f"Using executable directory base: {candidate_base}")
if not candidate_base:
candidate_base = os.path.abspath(os.path.join(os.getcwd(), 'fosslight_dc_bin'))
install_dir = candidate_base
else:
logger.debug(f"Resolved install_dir: {install_dir}")
bin_dir = os.path.join(install_dir, 'dependency-check', 'bin')
if sys.platform.startswith('win'):
dc_path = os.path.join(bin_dir, 'dependency-check.bat')
else:
dc_path = os.path.join(bin_dir, 'dependency-check.sh')

# Check if dependency-check already exists
if os.path.exists(dc_path):
try:
result = subprocess.run([dc_path, '--version'], capture_output=True, text=True, timeout=10)
if result.returncode == 0:
logger.debug("dependency-check already installed and working")
# If we detected an existing root via env, retain it, else set home now.
if forced_env:
os.environ['DEPENDENCY_CHECK_HOME'] = env_home_abs
else:
os.environ['DEPENDENCY_CHECK_HOME'] = os.path.join(install_dir, 'dependency-check')
os.environ['DEPENDENCY_CHECK_VERSION'] = DEPENDENCY_CHECK_VERSION
return
except (subprocess.TimeoutExpired, FileNotFoundError) as ex:
logger.debug(f"Exception in dependency-check --version: {ex}")
pass

# Download URL
download_url = (f"https://github.com/dependency-check/DependencyCheck/releases/"
f"download/v{DEPENDENCY_CHECK_VERSION}/"
f"dependency-check-{DEPENDENCY_CHECK_VERSION}-release.zip")

os.makedirs(install_dir, exist_ok=True)
logger.info(f"Downloading dependency-check {DEPENDENCY_CHECK_VERSION} from {download_url} ...")

# Download and extract
with urllib.request.urlopen(download_url) as response:
content = response.read()

with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_file:
tmp_file.write(content)
tmp_zip_path = tmp_file.name

with zipfile.ZipFile(tmp_zip_path, 'r') as zip_ref:
zip_ref.extractall(install_dir)
os.unlink(tmp_file.name)

# Make shell scripts executable
if os.path.exists(bin_dir):
if sys.platform.startswith('win'):
# Windows: .bat files only
scripts = ["dependency-check.bat"]
else:
# Linux/macOS: .sh files only
scripts = ["dependency-check.sh", "completion-for-dependency-check.sh"]

for script in scripts:
script_path = os.path.join(bin_dir, script)
if os.path.exists(script_path):
st = os.stat(script_path)
os.chmod(script_path, st.st_mode | stat.S_IEXEC)

logger.info("✅ OWASP dependency-check installed successfully!")
logger.info(f"Installed to: {os.path.join(install_dir, 'dependency-check')}")

# Set environment variables after successful installation
os.environ['DEPENDENCY_CHECK_VERSION'] = DEPENDENCY_CHECK_VERSION
os.environ['DEPENDENCY_CHECK_HOME'] = os.path.join(install_dir, 'dependency-check')

return True

except Exception as e:
logger.error(f"Failed to install dependency-check: {e}")
logger.info("dependency-check can be installed manually from: https://github.com/dependency-check/DependencyCheck/releases")
return False


def _auto_install_dependencies():
"""Auto-install required dependencies if not present."""
# Only run this once per session
if hasattr(_auto_install_dependencies, '_already_run'):
# Static path always used; environment overrides are ignored now.
_PKG_DIR = os.path.dirname(__file__)
_DC_HOME = os.path.join(_PKG_DIR, 'third_party', 'dependency-check')

# Fallback: project root layout (editable install) or current working directory
if not os.path.isdir(_DC_HOME):
_PROJECT_ROOT = os.path.abspath(os.path.join(_PKG_DIR, '..', '..'))
candidate = os.path.join(_PROJECT_ROOT, 'third_party', 'dependency-check')
if os.path.isdir(candidate):
_DC_HOME = candidate
else:
cwd_candidate = os.path.join(os.getcwd(), 'third_party', 'dependency-check')
if os.path.isdir(cwd_candidate):
_DC_HOME = cwd_candidate
if not os.path.isdir(_DC_HOME) and getattr(sys, 'frozen', False):
# Frozen executable scenario (PyInstaller onefile): check exe dir and _MEIPASS temp dir.
exe_dir = os.path.dirname(os.path.abspath(sys.executable))
exe_candidate = os.path.join(exe_dir, 'third_party', 'dependency-check')
if os.path.isdir(exe_candidate):
_DC_HOME = exe_candidate
else:
tmp_root = getattr(sys, '_MEIPASS', '')
if tmp_root:
tmp_candidate = os.path.join(tmp_root, 'third_party', 'dependency-check')
if os.path.isdir(tmp_candidate):
_DC_HOME = tmp_candidate


def get_dependency_check_script():
"""Return path to static dependency-check CLI script or None if missing."""
bin_dir = os.path.join(_DC_HOME, 'bin')
if sys.platform.startswith('win'):
script = os.path.join(bin_dir, 'dependency-check.bat')
else:
script = os.path.join(bin_dir, 'dependency-check.sh')
return script if os.path.isfile(script) else None


def _set_version_env(script_path):
"""Attempt to run '--version' to populate DEPENDENCY_CHECK_VERSION; ignore errors."""
if not script_path or not os.path.exists(script_path):
return
_auto_install_dependencies._already_run = True

try:
# Install binary version
_install_dependency_check()
result = subprocess.run([script_path, '--version'], capture_output=True, text=True, timeout=8)
if result.returncode == 0:
version_line = (result.stdout or '').strip().splitlines()[-1]
if version_line:
os.environ['DEPENDENCY_CHECK_VERSION'] = version_line
except Exception as ex:
logger.debug(f"Could not obtain dependency-check version: {ex}")


def _init_static_dependency_check():
if not os.path.isdir(_DC_HOME):
logger.info("Dependency-check not found under third_party/dependency-check.")
return
os.environ['DEPENDENCY_CHECK_HOME'] = _DC_HOME
script = get_dependency_check_script()
_set_version_env(script)
logger.debug(f"dependency-check home set to: {_DC_HOME}")

logger.info(f"✅ dependency-check setup completed with version {DEPENDENCY_CHECK_VERSION}")
except Exception as e:
logger.warning(f"Auto-install failed: {e}")

# Perform lightweight initialization (no network, no extraction)
_init_static_dependency_check()

# Auto-install on import
_auto_install_dependencies()
__all__ = [
'get_dependency_check_script'
]
17 changes: 7 additions & 10 deletions src/fosslight_binary/_jar_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import logging
import json
import os
import sys
import subprocess
from fosslight_binary import get_dependency_check_script
import fosslight_util.constant as constant
from fosslight_binary._binary import BinaryItem, VulnerabilityItem, is_package_dir
from fosslight_util.oss_item import OssItem
Expand Down Expand Up @@ -188,16 +188,13 @@ def analyze_jar_file(path_to_find_bin, path_to_exclude):
success = True
json_file = ""

# Use fixed install path: ./fosslight_dc_bin/dependency-check/bin/dependency-check.sh or .bat
if sys.platform.startswith('win'):
depcheck_path = os.path.abspath(os.path.join(os.getcwd(), 'fosslight_dc_bin', 'dependency-check', 'bin', 'dependency-check.bat'))
elif sys.platform.startswith('linux'):
depcheck_path = os.path.abspath(os.path.join(os.getcwd(), 'fosslight_dc_bin', 'dependency-check', 'bin', 'dependency-check.sh'))
elif sys.platform.startswith('darwin'):
depcheck_path = os.path.abspath(os.path.join(os.getcwd(), 'dependency-check'))

depcheck_path = get_dependency_check_script()
if not depcheck_path:
logger.info('dependency-check script not available. JAR OSS info will be skipped.')
success = False
return owasp_items, vulnerability_items, success
if not (os.path.isfile(depcheck_path) and os.access(depcheck_path, os.X_OK)):
logger.error(f'dependency-check script not found or not executable at {depcheck_path}')
logger.info(f'dependency-check script found but not executable: {depcheck_path}. Skipping.')
success = False
return owasp_items, vulnerability_items, success

Expand Down
Loading