Merge branch 'release/v5.0.4'

This commit is contained in:
Ivan Kravets
2020-12-30 13:23:11 +02:00
30 changed files with 257 additions and 146 deletions

View File

@ -15,7 +15,7 @@ jobs:
with: with:
submodules: "recursive" submodules: "recursive"
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
@ -26,7 +26,8 @@ jobs:
- name: Run on Linux - name: Run on Linux
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
env: env:
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,siwigsm,intel_mcs51,aceinna_imu" PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,intel_mcs51"
run: | run: |
# ChipKIT issue: install 32-bit support for GCC PIC32 # ChipKIT issue: install 32-bit support for GCC PIC32
sudo apt-get install libc6-i386 sudo apt-get install libc6-i386
@ -40,7 +41,8 @@ jobs:
- name: Run on macOS - name: Run on macOS
if: startsWith(matrix.os, 'macos') if: startsWith(matrix.os, 'macos')
env: env:
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,siwigsm,microchippic32,gd32v,nuclei,lattice_ice40" PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,microchippic32,lattice_ice40,gd32v"
run: | run: |
df -h df -h
tox -e testexamples tox -e testexamples
@ -50,7 +52,8 @@ jobs:
env: env:
PLATFORMIO_CORE_DIR: C:/pio PLATFORMIO_CORE_DIR: C:/pio
PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,siwigsm,riscv_gap" PIO_INSTALL_DEVPLATFORMS_OWNERNAMES: "platformio"
PIO_INSTALL_DEVPLATFORMS_IGNORE: "ststm8,infineonxmc,riscv_gap"
run: | run: |
tox -e testexamples tox -e testexamples

View File

@ -8,6 +8,21 @@ PlatformIO Core 5
**A professional collaborative platform for embedded development** **A professional collaborative platform for embedded development**
5.0.4 (2020-12-30)
~~~~~~~~~~~~~~~~~~
- Added "Core" suffix when showing PlatformIO Core version using ``pio --version`` command
- Improved ``.ccls`` configuration file for Emacs, Vim, and Sublime Text integrations
- Updated analysis tools:
* `Cppcheck <https://docs.platformio.org/page/plus/check-tools/cppcheck.html>`__ v2.3 with improved C++ parser and several new MISRA rules
* `PVS-Studio <https://docs.platformio.org/page/plus/check-tools/pvs-studio.html>`__ v7.11 with new diagnostics and updated mass suppression mechanism
- Show a warning message about deprecated support for Python 2 and Python 3.5
- Do not provide "intelliSenseMode" option when generating configuration for VSCode C/C++ extension
- Fixed a "git-sh-setup: file not found" error when installing project dependencies from Git VCS (`issue #3740 <https://github.com/platformio/platformio-core/issues/3740>`_)
- Fixed an issue with package publishing on Windows when Unix permissions are not preserved (`issue #3776 <https://github.com/platformio/platformio-core/issues/3776>`_)
5.0.3 (2020-11-12) 5.0.3 (2020-11-12)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -128,7 +143,8 @@ Please check `Migration guide from 4.x to 5.0 <https://docs.platformio.org/page/
- Display system-wide information using a new `pio system info <https://docs.platformio.org/page/core/userguide/system/cmd_info.html>`__ command (`issue #3521 <https://github.com/platformio/platformio-core/issues/3521>`_) - Display system-wide information using a new `pio system info <https://docs.platformio.org/page/core/userguide/system/cmd_info.html>`__ command (`issue #3521 <https://github.com/platformio/platformio-core/issues/3521>`_)
- Remove unused data using a new `pio system prune <https://docs.platformio.org/page/core/userguide/system/cmd_prune.html>`__ command (`issue #3522 <https://github.com/platformio/platformio-core/issues/3522>`_) - Remove unused data using a new `pio system prune <https://docs.platformio.org/page/core/userguide/system/cmd_prune.html>`__ command (`issue #3522 <https://github.com/platformio/platformio-core/issues/3522>`_)
- Show ignored project environments only in the verbose mode (`issue #3641 <https://github.com/platformio/platformio-core/issues/3641>`_) - Show ignored project environments only in the verbose mode (`issue #3641 <https://github.com/platformio/platformio-core/issues/3641>`_)
- Do not escape compiler arguments in VSCode template on Windows. - Do not escape compiler arguments in VSCode template on Windows
- Drop support for Python 2 and 3.5.
.. _release_notes_4: .. _release_notes_4:

2
docs

Submodule docs updated: 8d9e8ef02b...9db46dccef

View File

@ -14,7 +14,7 @@
import sys import sys
VERSION = (5, 0, 3) VERSION = (5, 0, 4)
__version__ = ".".join([str(s) for s in VERSION]) __version__ = ".".join([str(s) for s in VERSION])
__title__ = "platformio" __title__ = "platformio"
@ -31,11 +31,11 @@ __description__ = (
) )
__url__ = "https://platformio.org" __url__ = "https://platformio.org"
__author__ = "PlatformIO" __author__ = "PlatformIO Labs"
__email__ = "contact@platformio.org" __email__ = "contact@piolabs.com"
__license__ = "Apache Software License" __license__ = "Apache Software License"
__copyright__ = "Copyright 2014-present PlatformIO" __copyright__ = "Copyright 2014-present PlatformIO Labs"
__accounts_api__ = "https://api.accounts.platformio.org" __accounts_api__ = "https://api.accounts.platformio.org"
__registry_api__ = [ __registry_api__ = [
@ -51,9 +51,9 @@ __core_packages__ = {
"contrib-pysite": "~2.%d%d.0" % (sys.version_info.major, sys.version_info.minor), "contrib-pysite": "~2.%d%d.0" % (sys.version_info.major, sys.version_info.minor),
"tool-unity": "~1.20500.0", "tool-unity": "~1.20500.0",
"tool-scons": "~2.20501.7" if sys.version_info.major == 2 else "~4.40001.0", "tool-scons": "~2.20501.7" if sys.version_info.major == 2 else "~4.40001.0",
"tool-cppcheck": "~1.210.0", "tool-cppcheck": "~1.230.0",
"tool-clangtidy": "~1.100000.0", "tool-clangtidy": "~1.100000.0",
"tool-pvs-studio": "~7.9.0", "tool-pvs-studio": "~7.11.0",
} }
__check_internet_hosts__ = [ __check_internet_hosts__ = [

View File

@ -33,7 +33,7 @@ except: # pylint: disable=bare-except
@click.command( @click.command(
cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"]) cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"])
) )
@click.version_option(__version__, prog_name="PlatformIO") @click.version_option(__version__, prog_name="PlatformIO Core")
@click.option("--force", "-f", is_flag=True, help="DEPRECATE") @click.option("--force", "-f", is_flag=True, help="DEPRECATE")
@click.option("--caller", "-c", help="Caller ID (service)") @click.option("--caller", "-c", help="Caller ID (service)")
@click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters") @click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters")

View File

@ -167,6 +167,29 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
if os.path.isfile(f): if os.path.isfile(f):
os.remove(f) os.remove(f)
@staticmethod
def is_check_successful(cmd_result):
return cmd_result["returncode"] == 0
def execute_check_cmd(self, cmd):
result = proc.exec_command(
cmd,
stdout=proc.LineBufferedAsyncPipe(self.on_tool_output),
stderr=proc.LineBufferedAsyncPipe(self.on_tool_output),
)
if not self.is_check_successful(result):
click.echo(
"\nError: Failed to execute check command! Exited with code %d."
% result["returncode"]
)
if self.options.get("verbose"):
click.echo(result["out"])
click.echo(result["err"])
self._bad_input = True
return result
@staticmethod @staticmethod
def get_project_target_files(patterns): def get_project_target_files(patterns):
c_extension = (".c",) c_extension = (".c",)
@ -200,11 +223,7 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
if self.options.get("verbose"): if self.options.get("verbose"):
click.echo(" ".join(cmd)) click.echo(" ".join(cmd))
proc.exec_command( self.execute_check_cmd(cmd)
cmd,
stdout=proc.LineBufferedAsyncPipe(self.on_tool_output),
stderr=proc.LineBufferedAsyncPipe(self.on_tool_output),
)
else: else:
if self.options.get("verbose"): if self.options.get("verbose"):

View File

@ -49,6 +49,12 @@ class ClangtidyCheckTool(CheckToolBase):
return DefectItem(severity, category, message, file_, line, column, defect_id) return DefectItem(severity, category, message, file_, line, column, defect_id)
@staticmethod
def is_check_successful(cmd_result):
# Note: Clang-Tidy returns 1 for not critical compilation errors,
# so 0 and 1 are only acceptable values
return cmd_result["returncode"] < 2
def configure_command(self): def configure_command(self):
tool_path = join(get_core_package_dir("tool-clangtidy"), "clang-tidy") tool_path = join(get_core_package_dir("tool-clangtidy"), "clang-tidy")

View File

@ -109,7 +109,7 @@ class CppcheckCheckTool(CheckToolBase):
cmd = [ cmd = [
tool_path, tool_path,
"--addon-python=%s" % proc.get_pythonexe_path(), "--addon-python=%s" % proc.get_pythonexe_path(),
"--error-exitcode=1", "--error-exitcode=3",
"--verbose" if self.options.get("verbose") else "--quiet", "--verbose" if self.options.get("verbose") else "--quiet",
] ]
@ -220,6 +220,11 @@ class CppcheckCheckTool(CheckToolBase):
if os.path.isfile(dump_file): if os.path.isfile(dump_file):
os.remove(dump_file) os.remove(dump_file)
@staticmethod
def is_check_successful(cmd_result):
# Cppcheck is configured to return '3' if a defect is found
return cmd_result["returncode"] in (0, 3)
def check(self, on_defect_callback=None): def check(self, on_defect_callback=None):
self._on_defect_callback = on_defect_callback self._on_defect_callback = on_defect_callback
project_files = self.get_project_target_files(self.options["patterns"]) project_files = self.get_project_target_files(self.options["patterns"])
@ -238,11 +243,7 @@ class CppcheckCheckTool(CheckToolBase):
if self.options.get("verbose"): if self.options.get("verbose"):
click.echo(" ".join(cmd)) click.echo(" ".join(cmd))
proc.exec_command( self.execute_check_cmd(cmd)
cmd,
stdout=proc.LineBufferedAsyncPipe(self.on_tool_output),
stderr=proc.LineBufferedAsyncPipe(self.on_tool_output),
)
self.clean_up() self.clean_up()

View File

@ -52,6 +52,11 @@ class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-at
) )
) )
def tool_output_filter(self, line):
if "license was not entered" in line.lower():
self._bad_input = True
return line
def _process_defects(self, defects): def _process_defects(self, defects):
for defect in defects: for defect in defects:
if not isinstance(defect, DefectItem): if not isinstance(defect, DefectItem):
@ -203,6 +208,12 @@ class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-at
if os.path.isdir(self._tmp_dir): if os.path.isdir(self._tmp_dir):
shutil.rmtree(self._tmp_dir) shutil.rmtree(self._tmp_dir)
@staticmethod
def is_check_successful(cmd_result):
return (
"license" not in cmd_result["err"].lower() and cmd_result["returncode"] == 0
)
def check(self, on_defect_callback=None): def check(self, on_defect_callback=None):
self._on_defect_callback = on_defect_callback self._on_defect_callback = on_defect_callback
for scope, files in self.get_project_target_files( for scope, files in self.get_project_target_files(
@ -219,11 +230,8 @@ class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-at
self._bad_input = True self._bad_input = True
continue continue
result = proc.exec_command(cmd) result = self.execute_check_cmd(cmd)
# pylint: disable=unsupported-membership-test if result["returncode"] != 0:
if result["returncode"] != 0 or "license" in result["err"].lower():
self._bad_input = True
click.echo(result["err"])
continue continue
self._process_defects(self.parse_defects(self._tmp_output_file)) self._process_defects(self.parse_defects(self._tmp_output_file))

View File

@ -23,6 +23,7 @@ from platformio.clients.registry import RegistryClient
from platformio.compat import ensure_python3 from platformio.compat import ensure_python3
from platformio.package.meta import PackageSpec, PackageType from platformio.package.meta import PackageSpec, PackageType
from platformio.package.pack import PackagePacker from platformio.package.pack import PackagePacker
from platformio.package.unpack import FileUnpacker, TARArchiver
def validate_datetime(ctx, param, value): # pylint: disable=unused-argument def validate_datetime(ctx, param, value): # pylint: disable=unused-argument
@ -81,6 +82,17 @@ def package_pack(package, output):
) )
def package_publish(package, owner, released_at, private, notify): def package_publish(package, owner, released_at, private, notify):
assert ensure_python3() assert ensure_python3()
# publish .tar.gz instantly without repacking
if not os.path.isdir(package) and isinstance(
FileUnpacker.new_archiver(package), TARArchiver
):
response = RegistryClient().publish_package(
package, owner, released_at, private, notify
)
click.secho(response.get("message"), fg="green")
return
with tempfile.TemporaryDirectory() as tmp_dir: # pylint: disable=no-member with tempfile.TemporaryDirectory() as tmp_dir: # pylint: disable=no-member
with fs.cd(tmp_dir): with fs.cd(tmp_dir):
p = PackagePacker(package) p = PackagePacker(package)

View File

@ -62,10 +62,11 @@ def ci_strings_are_equal(a, b):
def ensure_python3(raise_exception=True): def ensure_python3(raise_exception=True):
if not raise_exception or not PY2: compatible = sys.version_info >= (3, 6)
return not PY2 if not raise_exception or compatible:
return compatible
raise UserSideException( raise UserSideException(
"Python 3.5 or later is required for this operation. \n" "Python 3.6 or later is required for this operation. \n"
"Please install the latest Python 3 and reinstall PlatformIO Core using " "Please install the latest Python 3 and reinstall PlatformIO Core using "
"installation script:\n" "installation script:\n"
"https://docs.platformio.org/page/core/installation.html" "https://docs.platformio.org/page/core/installation.html"

View File

@ -1,22 +1,12 @@
% import re
% STD_RE = re.compile(r"\-std=[a-z\+]+(\w+)")
% cc_stds = STD_RE.findall(cc_flags)
% cxx_stds = STD_RE.findall(cxx_flags)
%
%
{{ cxx_path }} {{ cxx_path }}
% if cc_stds: {{"%c"}} {{ !cc_flags }}
{{"%c"}} -std=c{{ cc_stds[-1] }} {{"%cpp"}} {{ !cxx_flags }}
% end
% if cxx_stds:
{{"%cpp"}} -std=c++{{ cxx_stds[-1] }}
% end
% for include in filter_includes(includes): % for include in filter_includes(includes):
-I{{ include }} -I{{ !include }}
% end % end
% for define in defines: % for define in defines:
-D{{ define }} -D{{ !define }}
% end % end

View File

@ -1,6 +0,0 @@
% for include in filter_includes(includes):
-I{{include}}
% end
% for define in defines:
-D{{!define}}
% end

View File

@ -1,22 +1,12 @@
% import re
% STD_RE = re.compile(r"\-std=[a-z\+]+(\w+)")
% cc_stds = STD_RE.findall(cc_flags)
% cxx_stds = STD_RE.findall(cxx_flags)
%
%
{{ cxx_path }} {{ cxx_path }}
% if cc_stds: {{"%c"}} {{ !cc_flags }}
{{"%c"}} -std=c{{ cc_stds[-1] }} {{"%cpp"}} {{ !cxx_flags }}
% end
% if cxx_stds:
{{"%cpp"}} -std=c++{{ cxx_stds[-1] }}
% end
% for include in filter_includes(includes): % for include in filter_includes(includes):
-I{{ include }} -I{{ !include }}
% end % end
% for define in defines: % for define in defines:
-D{{ define }} -D{{ !define }}
% end % end

View File

@ -1,20 +1,10 @@
% import re {{ cxx_path }}
% STD_RE = re.compile(r"\-std=[a-z\+]+(\w+)")
% cc_stds = STD_RE.findall(cc_flags)
% cxx_stds = STD_RE.findall(cxx_flags)
%
%
clang
% if cc_stds: {{"%c"}} {{ !cc_flags }}
{{"%c"}} -std=c{{ cc_stds[-1] }} {{"%cpp"}} {{ !cxx_flags }}
% end
% if cxx_stds:
{{"%cpp"}} -std=c++{{ cxx_stds[-1] }}
% end
% for include in filter_includes(includes): % for include in filter_includes(includes):
-I{{ include }} -I{{ !include }}
% end % end
% for define in defines: % for define in defines:

View File

@ -1,6 +0,0 @@
% for include in filter_includes(includes):
-I"{{include}}"
% end
% for define in defines:
-D{{!define}}
% end

View File

@ -1,9 +0,0 @@
% _defines = " ".join(["-D%s" % d.replace(" ", "\\\\ ") for d in defines])
{
"execPath": "{{ cxx_path }}",
"gccDefaultCFlags": "-fsyntax-only {{! cc_flags.replace(' -MMD ', ' ').replace('"', '\\"') }} {{ !_defines.replace('"', '\\"') }}",
"gccDefaultCppFlags": "-fsyntax-only {{! cxx_flags.replace(' -MMD ', ' ').replace('"', '\\"') }} {{ !_defines.replace('"', '\\"') }}",
"gccErrorLimit": 15,
"gccIncludePaths": "{{ ','.join(filter_includes(includes)) }}",
"gccSuppressWarnings": false
}

View File

@ -83,20 +83,15 @@
% forced_includes = _find_forced_includes( % forced_includes = _find_forced_includes(
% filter_args(cc_m_flags, ["-include", "-imacros"]), cleaned_includes) % filter_args(cc_m_flags, ["-include", "-imacros"]), cleaned_includes)
% %
//
// !!! WARNING !!! AUTO-GENERATED FILE!
// PLEASE DO NOT MODIFY IT AND USE "platformio.ini":
// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags
//
{ {
"configurations": [ "configurations": [
{ {
"name": "!!! WARNING !!! AUTO-GENERATED FILE, PLEASE DO NOT MODIFY IT AND USE https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags" "name": "PlatformIO",
},
{
% if systype == "windows":
"name": "Win32",
% elif systype == "darwin":
"name": "Mac",
"macFrameworkPath": [],
% else:
"name": "Linux",
% end
"includePath": [ "includePath": [
% for include in cleaned_includes: % for include in cleaned_includes:
"{{ include }}", "{{ include }}",
@ -118,9 +113,6 @@
% end % end
"" ""
], ],
% if compiler_type == "gcc":
"intelliSenseMode": "gcc-x64",
% end
% if cc_stds: % if cc_stds:
"cStandard": "c{{ cc_stds[-1] }}", "cStandard": "c{{ cc_stds[-1] }}",
% end % end

View File

@ -27,6 +27,7 @@ from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib.command import lib_update as cmd_lib_update from platformio.commands.lib.command import lib_update as cmd_lib_update
from platformio.commands.platform import platform_update as cmd_platform_update from platformio.commands.platform import platform_update as cmd_platform_update
from platformio.commands.upgrade import get_latest_version from platformio.commands.upgrade import get_latest_version
from platformio.compat import ensure_python3
from platformio.package.manager.core import update_core_packages from platformio.package.manager.core import update_core_packages
from platformio.package.manager.library import LibraryPackageManager from platformio.package.manager.library import LibraryPackageManager
from platformio.package.manager.platform import PlatformPackageManager from platformio.package.manager.platform import PlatformPackageManager
@ -43,8 +44,25 @@ def on_platformio_start(ctx, force, caller):
set_caller(caller) set_caller(caller)
telemetry.on_command() telemetry.on_command()
if not PlatformioCLI.in_silence(): if PlatformioCLI.in_silence():
after_upgrade(ctx) return
after_upgrade(ctx)
if not ensure_python3(raise_exception=False):
click.secho(
"""
Python 2 and Python 3.5 are not compatible with PlatformIO Core 5.0.
Please check the migration guide on how to fix this warning message:
""",
fg="yellow",
)
click.secho(
"https://docs.platformio.org/en/latest/core/migration.html"
"#drop-support-for-python-2-and-3-5",
fg="blue",
)
click.echo("")
def on_platformio_end(ctx, result): # pylint: disable=unused-argument def on_platformio_end(ctx, result): # pylint: disable=unused-argument

View File

@ -153,7 +153,7 @@ class PackageManagerInstallMixin(object):
finally: finally:
if os.path.isdir(tmp_dir): if os.path.isdir(tmp_dir):
try: try:
shutil.rmtree(tmp_dir) fs.rmtree(tmp_dir)
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
pass pass

View File

@ -104,7 +104,7 @@ class PackageManagerUpdateMixin(object):
outdated = self.outdated(pkg, to_spec) outdated = self.outdated(pkg, to_spec)
if not silent: if not silent:
self.print_outdated_state(outdated, show_incompatible) self.print_outdated_state(outdated, only_check, show_incompatible)
if only_check or not outdated.is_outdated(allow_incompatible=False): if only_check or not outdated.is_outdated(allow_incompatible=False):
return pkg return pkg
@ -116,24 +116,39 @@ class PackageManagerUpdateMixin(object):
self.unlock() self.unlock()
@staticmethod @staticmethod
def print_outdated_state(outdated, show_incompatible=True): def print_outdated_state(outdated, only_check, show_incompatible):
if outdated.detached: if outdated.detached:
return click.echo("[%s]" % (click.style("Detached", fg="yellow"))) return click.echo("[%s]" % (click.style("Detached", fg="yellow")))
if ( if (
not outdated.latest not outdated.latest
or outdated.current == outdated.latest or outdated.current == outdated.latest
or (not show_incompatible and outdated.current == outdated.wanted) or (not show_incompatible and outdated.current == outdated.wanted)
): ):
return click.echo("[%s]" % (click.style("Up-to-date", fg="green"))) return click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
if outdated.wanted and outdated.current == outdated.wanted: if outdated.wanted and outdated.current == outdated.wanted:
return click.echo( return click.echo(
"[%s]" % (click.style("Incompatible %s" % outdated.latest, fg="yellow")) "[%s]" % (click.style("Incompatible %s" % outdated.latest, fg="yellow"))
) )
if only_check:
return click.echo(
"[%s]"
% (
click.style(
"Outdated %s" % str(outdated.wanted or outdated.latest),
fg="red",
)
)
)
return click.echo( return click.echo(
"[%s]" "[%s]"
% ( % (
click.style( click.style(
"Outdated %s" % str(outdated.wanted or outdated.latest), fg="red" "Updating to %s" % str(outdated.wanted or outdated.latest),
fg="green",
) )
) )
) )

View File

@ -143,7 +143,8 @@ class ExampleSchema(StrictSchema):
validate=[ validate=[
validate.Length(min=1, max=255), validate.Length(min=1, max=255),
validate.Regexp( validate.Regexp(
r"^[a-zA-Z\d\-\_/]+$", error="Only [a-zA-Z0-9-_/] chars are allowed" r"^[a-zA-Z\d\-\_/\. ]+$",
error="Only [a-zA-Z0-9-_/. ] chars are allowed",
), ),
], ],
) )

View File

@ -20,8 +20,8 @@ import tarfile
import tempfile import tempfile
from platformio import fs from platformio import fs
from platformio.compat import ensure_python3 from platformio.compat import WINDOWS, ensure_python3
from platformio.package.exception import PackageException from platformio.package.exception import PackageException, UserSideException
from platformio.package.manifest.parser import ManifestFileType, ManifestParserFactory from platformio.package.manifest.parser import ManifestFileType, ManifestParserFactory
from platformio.package.manifest.schema import ManifestSchema from platformio.package.manifest.schema import ManifestSchema
from platformio.package.meta import PackageItem from platformio.package.meta import PackageItem
@ -117,6 +117,12 @@ class PackagePacker(object):
# if zip/tar.gz -> unpack to tmp dir # if zip/tar.gz -> unpack to tmp dir
if not os.path.isdir(src): if not os.path.isdir(src):
if WINDOWS:
raise UserSideException(
"Packaging from an archive does not work on Windows OS. Please "
"extract data from `%s` manually and pack a folder instead"
% src
)
with FileUnpacker(src) as fu: with FileUnpacker(src) as fu:
assert fu.unpack(tmp_dir, silent=True) assert fu.unpack(tmp_dir, silent=True)
src = tmp_dir src = tmp_dir

View File

@ -134,27 +134,28 @@ class FileUnpacker(object):
self.path = path self.path = path
self._archiver = None self._archiver = None
def _init_archiver(self): def __enter__(self):
self._archiver = self.new_archiver(self.path)
return self
def __exit__(self, *args):
if self._archiver:
self._archiver.close()
@staticmethod
def new_archiver(path):
magic_map = { magic_map = {
b"\x1f\x8b\x08": TARArchiver, b"\x1f\x8b\x08": TARArchiver,
b"\x42\x5a\x68": TARArchiver, b"\x42\x5a\x68": TARArchiver,
b"\x50\x4b\x03\x04": ZIPArchiver, b"\x50\x4b\x03\x04": ZIPArchiver,
} }
magic_len = max(len(k) for k in magic_map) magic_len = max(len(k) for k in magic_map)
with open(self.path, "rb") as fp: with open(path, "rb") as fp:
data = fp.read(magic_len) data = fp.read(magic_len)
for magic, archiver in magic_map.items(): for magic, archiver in magic_map.items():
if data.startswith(magic): if data.startswith(magic):
return archiver(self.path) return archiver(path)
raise PackageException("Unknown archive type '%s'" % self.path) raise PackageException("Unknown archive type '%s'" % path)
def __enter__(self):
self._archiver = self._init_archiver()
return self
def __exit__(self, *args):
if self._archiver:
self._archiver.close()
def unpack( def unpack(
self, dest_dir=None, with_progress=True, check_unpacked=True, silent=False self, dest_dir=None, with_progress=True, check_unpacked=True, silent=False

View File

@ -12,17 +12,17 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import os
import re import re
from os.path import join import subprocess
from subprocess import CalledProcessError, check_call import sys
from sys import modules
from platformio import proc
from platformio.package.exception import ( from platformio.package.exception import (
PackageException, PackageException,
PlatformioException, PlatformioException,
UserSideException, UserSideException,
) )
from platformio.proc import exec_command
try: try:
from urllib.parse import urlparse from urllib.parse import urlparse
@ -51,7 +51,7 @@ class VCSClientFactory(object):
if not type_: if not type_:
raise VCSBaseException("VCS: Unknown repository type %s" % remote_url) raise VCSBaseException("VCS: Unknown repository type %s" % remote_url)
try: try:
obj = getattr(modules[__name__], "%sClient" % type_.title())( obj = getattr(sys.modules[__name__], "%sClient" % type_.title())(
src_dir, remote_url, tag, silent src_dir, remote_url, tag, silent
) )
assert isinstance(obj, VCSClientBase) assert isinstance(obj, VCSClientBase)
@ -86,7 +86,7 @@ class VCSClientBase(object):
@property @property
def storage_dir(self): def storage_dir(self):
return join(self.src_dir, "." + self.command) return os.path.join(self.src_dir, "." + self.command)
def export(self): def export(self):
raise NotImplementedError raise NotImplementedError
@ -108,17 +108,19 @@ class VCSClientBase(object):
args = [self.command] + args args = [self.command] + args
if "cwd" not in kwargs: if "cwd" not in kwargs:
kwargs["cwd"] = self.src_dir kwargs["cwd"] = self.src_dir
if "env" not in kwargs:
kwargs["env"] = os.environ
try: try:
check_call(args, **kwargs) subprocess.check_call(args, **kwargs)
return True return True
except CalledProcessError as e: except subprocess.CalledProcessError as e:
raise VCSBaseException("VCS: Could not process command %s" % e.cmd) raise VCSBaseException("VCS: Could not process command %s" % e.cmd)
def get_cmd_output(self, args, **kwargs): def get_cmd_output(self, args, **kwargs):
args = [self.command] + args args = [self.command] + args
if "cwd" not in kwargs: if "cwd" not in kwargs:
kwargs["cwd"] = self.src_dir kwargs["cwd"] = self.src_dir
result = exec_command(args, **kwargs) result = proc.exec_command(args, **kwargs)
if result["returncode"] == 0: if result["returncode"] == 0:
return result["out"].strip() return result["out"].strip()
raise VCSBaseException( raise VCSBaseException(
@ -129,6 +131,28 @@ class VCSClientBase(object):
class GitClient(VCSClientBase): class GitClient(VCSClientBase):
command = "git" command = "git"
_configured = False
def __init__(self, *args, **kwargs):
self.configure()
super(GitClient, self).__init__(*args, **kwargs)
@classmethod
def configure(cls):
if cls._configured:
return True
cls._configured = True
try:
result = proc.exec_command([cls.command, "--exec-path"])
if result["returncode"] != 0:
return False
path = result["out"].strip()
if path:
proc.append_env_path("PATH", path)
return True
except subprocess.CalledProcessError:
pass
return False
def check_client(self): def check_client(self):
try: try:
@ -173,7 +197,7 @@ class GitClient(VCSClientBase):
if self.tag: if self.tag:
args += ["--branch", self.tag] args += ["--branch", self.tag]
args += [self.remote_url, self.src_dir] args += [self.remote_url, self.src_dir]
assert self.run_cmd(args) assert self.run_cmd(args, cwd=os.getcwd())
if is_commit: if is_commit:
assert self.run_cmd(["reset", "--hard", self.tag]) assert self.run_cmd(["reset", "--hard", self.tag])
return self.run_cmd( return self.run_cmd(

View File

@ -20,6 +20,7 @@ from threading import Thread
from platformio import exception from platformio import exception
from platformio.compat import ( from platformio.compat import (
PY2,
WINDOWS, WINDOWS,
get_filesystem_encoding, get_filesystem_encoding,
get_locale_encoding, get_locale_encoding,
@ -125,7 +126,9 @@ def exec_command(*args, **kwargs):
result[s[3:]] = kwargs[s].get_buffer() result[s[3:]] = kwargs[s].get_buffer()
for k, v in result.items(): for k, v in result.items():
if isinstance(result[k], bytes): if PY2 and isinstance(v, unicode): # pylint: disable=undefined-variable
result[k] = v.encode()
elif not PY2 and isinstance(result[k], bytes):
try: try:
result[k] = result[k].decode( result[k] = result[k].decode(
get_locale_encoding() or get_filesystem_encoding() get_locale_encoding() or get_filesystem_encoding()
@ -203,3 +206,11 @@ def where_is_program(program, envpath=None):
return os.path.join(bin_dir, "%s.exe" % program) return os.path.join(bin_dir, "%s.exe" % program)
return program return program
def append_env_path(name, value):
cur_value = os.environ.get(name) or ""
if cur_value and value in cur_value.split(os.pathsep):
return cur_value
os.environ[name] = os.pathsep.join([cur_value, value])
return os.environ[name]

View File

@ -26,15 +26,25 @@ import click
envvar="PIO_INSTALL_DEVPLATFORMS_IGNORE", envvar="PIO_INSTALL_DEVPLATFORMS_IGNORE",
help="Ignore names split by comma", help="Ignore names split by comma",
) )
def main(desktop, ignore): @click.option(
"--ownernames",
envvar="PIO_INSTALL_DEVPLATFORMS_OWNERNAMES",
help="Filter dev-platforms by ownernames (split by comma)",
)
def main(desktop, ignore, ownernames):
platforms = json.loads( platforms = json.loads(
subprocess.check_output( subprocess.check_output(
["platformio", "platform", "search", "--json-output"] ["platformio", "platform", "search", "--json-output"]
).decode() ).decode()
) )
ignore = [n.strip() for n in (ignore or "").split(",") if n.strip()] ignore = [n.strip() for n in (ignore or "").split(",") if n.strip()]
ownernames = [n.strip() for n in (ownernames or "").split(",") if n.strip()]
for platform in platforms: for platform in platforms:
skip = [not desktop and platform["forDesktop"], platform["name"] in ignore] skip = [
not desktop and platform["forDesktop"],
platform["name"] in ignore,
ownernames and platform["ownername"] not in ownernames,
]
if any(skip): if any(skip):
continue continue
subprocess.check_call(["platformio", "platform", "install", platform["name"]]) subprocess.check_call(["platformio", "platform", "install", platform["name"]])

View File

@ -410,6 +410,22 @@ check_tool = pvs-studio
assert style == 0 assert style == 0
def test_check_pvs_studio_fails_without_license(clirunner, tmpdir):
config = DEFAULT_CONFIG + "\ncheck_tool = pvs-studio"
tmpdir.join("platformio.ini").write(config)
tmpdir.mkdir("src").join("main.c").write(TEST_CODE)
default_result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)])
verbose_result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir), "-v"])
assert default_result.exit_code != 0
assert "failed to perform check" in default_result.output.lower()
assert verbose_result.exit_code != 0
assert "license was not entered" in verbose_result.output.lower()
def test_check_embedded_platform_all_tools(clirunner, validate_cliresult, tmpdir): def test_check_embedded_platform_all_tools(clirunner, validate_cliresult, tmpdir):
config = """ config = """
[env:test] [env:test]

View File

@ -91,7 +91,7 @@ def test_check_and_update_libraries(clirunner, isolated_pio_core, validate_clire
assert "There are the new updates for libraries (ArduinoJson)" in result.output assert "There are the new updates for libraries (ArduinoJson)" in result.output
assert "Please wait while updating libraries" in result.output assert "Please wait while updating libraries" in result.output
assert re.search( assert re.search(
r"Updating bblanchon/ArduinoJson\s+6\.12\.0\s+\[Outdated [\d\.]+\]", r"Updating bblanchon/ArduinoJson\s+6\.12\.0\s+\[Updating to [\d\.]+\]",
result.output, result.output,
) )
@ -143,7 +143,9 @@ def test_check_and_update_platforms(clirunner, isolated_pio_core, validate_clire
validate_cliresult(result) validate_cliresult(result)
assert "There are the new updates for platforms (native)" in result.output assert "There are the new updates for platforms (native)" in result.output
assert "Please wait while updating platforms" in result.output assert "Please wait while updating platforms" in result.output
assert re.search(r"Updating native\s+0.0.0\s+\[Outdated [\d\.]+\]", result.output) assert re.search(
r"Updating native\s+0.0.0\s+\[Updating to [\d\.]+\]", result.output
)
# check updated version # check updated version
result = clirunner.invoke(cli_pio, ["platform", "list", "--json-output"]) result = clirunner.invoke(cli_pio, ["platform", "list", "--json-output"])