分类 技术 下的文章

你对如何让调试器变得更快产生过兴趣吗?本文将分享我们在为 Python 构建调试器时得到的一些经验。

整段故事讲的是我们在 Rookout 公司的团队为 Python 调试器开发不中断断点的经历,以及开发过程中得到的经验。我将在本月于旧金山举办的 PyBay 2019 上介绍有关 Python 调试过程的更多细节,但现在就让我们立刻开始这段故事。

Python 调试器的心脏:sys.set\_trace

在诸多可选的 Python 调试器中,使用最广泛的三个是:

  • pdb,它是 Python 标准库的一部分
  • PyDev,它是内嵌在 Eclipse 和 Pycharm 等 IDE 中的调试器
  • ipdb,它是 IPython 的调试器

Python 调试器的选择虽多,但它们几乎都基于同一个函数:sys.settrace。 值得一提的是, sys.settrace 可能也是 Python 标准库中最复杂的函数。

 title=

简单来讲,settrace 的作用是为解释器注册一个跟踪函数,它在下列四种情形发生时被调用:

  • 函数调用
  • 语句执行
  • 函数返回
  • 异常抛出

一个简单的跟踪函数看上去大概是这样:

def simple_tracer(frame, event, arg):
  co = frame.f_code
  func_name = co.co_name
  line_no = frame.f_lineno
  print("{e} {f} {l}".format(
e=event, f=func_name, l=line_no))
  return simple_tracer

在分析函数时我们首先关注的是参数和返回值,该跟踪函数的参数分别是:

  • frame,当前堆栈帧,它是包含当前函数执行时解释器里完整状态的对象
  • event,事件,它是一个值可能为 calllinereturnexception 的字符串
  • arg,参数,它的取值基于 event 的类型,是一个可选项

该跟踪函数的返回值是它自身,这是由于解释器需要持续跟踪两类跟踪函数:

  • 全局跟踪函数(每线程):该跟踪函数由当前线程调用 sys.settrace 来设置,并在解释器创建一个新的堆栈帧时被调用(即代码中发生函数调用时)。虽然没有现成的方式来为不同的线程设置跟踪函数,但你可以调用 threading.settrace 来为所有新创建的 threading 模块线程设置跟踪函数。
  • 局部跟踪函数(每一帧):解释器将该跟踪函数的值设置为全局跟踪函数创建帧时的返回值。同样也没有现成的方法能够在帧被创建时自动设置局部跟踪函数。

该机制的目的是让调试器对被跟踪的帧有更精确的把握,以减少对性能的影响。

简单三步构建调试器 (我们最初的设想)

仅仅依靠上文提到的内容,用自制的跟踪函数来构建一个真正的调试器似乎有些不切实际。幸运的是,Python 的标准调试器 pdb 是基于 Bdb 构建的,后者是 Python 标准库中专门用于构建调试器的基类。

基于 Bdb 的简易断点调试器看上去是这样的:

import bdb
import inspect

class Debugger(bdb.Bdb):
  def __init__(self):
      Bdb.__init__(self)
      self.breakpoints = dict()
      self.set_trace()

def set_breakpoint(self, filename, lineno, method):
  self.set_break(filename, lineno)
  try :
      self.breakpoints[(filename, lineno)].add(method)
  except KeyError:
      self.breakpoints[(filename, lineno)] = [method]

def user_line(self, frame):
  if not self.break_here(frame):
      return

  # Get filename and lineno from frame
  (filename, lineno, _, _, _) = inspect.getframeinfo(frame)

  methods = self.breakpoints[(filename, lineno)]
  for method in methods:
      method(frame)

这个调试器类的全部构成是:

  1. 继承 Bdb,定义一个简单的构造函数来初始化基类,并开始跟踪。
  2. 添加 set_breakpoint 方法,它使用 Bdb 来设置断点,并跟踪这些断点。
  3. 重载 Bdb 在当前用户行调用的 user_line 方法,该方法一定被一个断点调用,之后获取该断点的源位置,并调用已注册的断点。

这个简易的 Bdb 调试器效率如何呢?

Rookout 的目标是在生产级性能的使用场景下提供接近普通调试器的使用体验。那么,让我们来看看先前构建出来的简易调试器表现的如何。

为了衡量调试器的整体性能开销,我们使用如下两个简单的函数来进行测试,它们分别在不同的情景下执行了 1600 万次。请注意,在所有情景下断点都不会被执行。

def empty_method():
   pass

def simple_method():
   a = 1
   b = 2
   c = 3
   d = 4
   e = 5
   f = 6
   g = 7
   h = 8
   i = 9
   j = 10

在使用调试器的情况下需要大量的时间才能完成测试。糟糕的结果指明了,这个简陋 Bdb 调试器的性能还远不足以在生产环境中使用。

 title=

对调试器进行优化

降低调试器的额外开销主要有三种方法:

  1. 尽可能的限制局部跟踪:由于每一行代码都可能包含大量事件,局部跟踪比全局跟踪的开销要大得多。
  2. 优化 call 事件并尽快将控制权还给解释器:在 call 事件发生时调试器的主要工作是判断是否需要对该事件进行跟踪。
  3. 优化 line 事件并尽快将控制权还给解释器:在 line 事件发生时调试器的主要工作是判断我们在此处是否需要设置一个断点。

于是我们复刻了 Bdb 项目,精简特征、简化代码,针对使用场景进行优化。这些工作虽然得到了一些效果,但仍无法满足我们的需求。因此我们又继续进行了其它的尝试,将代码优化并迁移至 .pyx 使用 Cython 进行编译,可惜结果(如下图所示)依旧不够理想。最终,我们在深入了解 CPython 源码之后意识到,让跟踪过程快到满足生产需求是不可能的。

 title=

放弃 Bdb 转而尝试字节码操作

熬过先前对标准调试方法进行的试验-失败-再试验循环所带来的失望,我们将目光转向另一种选择:字节码操作。

Python 解释器的工作主要分为两个阶段:

  1. 将 Python 源码编译成 Python 字节码:这种(对人类而言)不可读的格式专为执行的效率而优化,它们通常缓存在我们熟知的 .pyc 文件当中。
  2. 遍历 解释器循环中的字节码: 在这一步中解释器会逐条的执行指令。

我们选择的模式是:使用字节码操作来设置没有全局额外开销的不中断断点。这种方式的实现首先需要在内存中的字节码里找到我们感兴趣的部分,然后在该部分的相关机器指令前插入一个函数调用。如此一来,解释器无需任何额外的工作即可实现我们的不中断断点。

这种方法并不依靠魔法来实现,让我们简要地举个例子。

首先定义一个简单的函数:

def multiply(a, b):
   result = a * b
   return result

inspect 模块(其包含了许多实用的单元)的文档里,我们得知可以通过访问 multiply.func_code.co_code 来获取函数的字节码:

'|\x00\x00|\x01\x00\x14}\x02\x00|\x02\x00S'

使用 Python 标准库中的 dis 模块可以翻译这些不可读的字符串。调用 dis.dis(multiply.func_code.co_code) 之后,我们就可以得到:

  4          0 LOAD_FAST               0 (a)
             3 LOAD_FAST               1 (b)
             6 BINARY_MULTIPLY    
             7 STORE_FAST              2 (result)

  5         10 LOAD_FAST               2 (result)
            13 RETURN_VALUE      

与直截了当的解决方案相比,这种方法让我们更靠近发生在调试器背后的事情。可惜 Python 并没有提供在解释器中修改函数字节码的方法。我们可以对函数对象进行重写,不过那样做的效率满足不了大多数实际的调试场景。最后我们不得不采用一种迂回的方式来使用原生拓展才能完成这一任务。

总结

在构建一个新工具时,总会学到许多事情的工作原理。这种刨根问底的过程能够使你的思路跳出桎梏,从而得到意料之外的解决方案。

在 Rookout 团队中构建不中断断点的这段时间里,我学到了许多有关编译器、调试器、服务器框架、并发模型等等领域的知识。如果你希望更深入的了解字节码操作,谷歌的开源项目 cloud-debug-python 为编辑字节码提供了一些工具。


via: https://opensource.com/article/19/8/debug-python

作者:Liran Haimovitch 选题:lujun9972 译者:caiichenr 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

转换文本的大小写可能非常繁琐,尤其是当你要避免无意间的拼写错误时。幸运的是,Linux 提供了一些命令,可以使工作变得非常容易。

有很多方法可以在 Linux 命令行中将文本从小写更改为大写,反之亦然。实际上,有一组这样的命令可以选择。这篇文章检验了一些最佳的命令来完成这项工作,以及你该如何让它们正常工作。

使用 tr

tr(translate)命令是在命令行或脚本中最容易使用的命令之一。如果你要确定要一串大写字符串,你只需将它传给 tr,如下所示:

$ echo Hello There | tr [:lower:] [:upper:]
HELLO THERE

下面是一个在脚本中使用这个命令的例子,当你要确保添加到文件中的所有文本都使用大写形式以保持一致性时(LCTT 译注:这里输入部门名称作为示例):

#!/bin/bash

echo -n "Enter department name: "
read dept
echo $dept | tr [:lower:] [:upper:] >> depts

将顺序切换为 [:upper:] [:lower:] 会产生相反的效果,将所有大写的部门名称都转换为小写:

echo $dept | tr [:upper:] [:lower:] >> depts

同样,你可以使用 sed 命令的 A-Za-z 字符串完成相同的操作:

echo $dept | tr a-z A-Z >> depts

毫无疑问,反转 a-zA-Z 字符串的顺序将产生相反的效果,将文本全部变为小写。

使用 awk

awk 命令可让你使用它的 touppertolower 选项执行相同的操作。上例脚本中的命令可以用这种方式代替:

echo $dept | awk '{print toupper($0)}' >> depts

相反操作(切换为小写)如下所示:

echo $dept | awk '{print tolower($0)}' >> depts

使用 sed

sed(stream editor)命令也可用于切换大小写。它与上面显示的两个命令中的第一个具有相同的效果。

echo $dept | sed 's/[a-z]/\U&/g' >> depts

从大写字母切换到小写字母只需将行尾附近的 U 替换为 L

echo $dept | sed 's/[A-Z]/\L&/g' >> depts

操作文件中的文本

awksed 都能更改整个文件的文本大小写。因此,你发现你的老板需要所有部门名称的小写么?没问题。只需带上文件名运行以下命令:

$ awk '{print tolower($0)}' depts
finance
billing
bookkeeping

如果要覆盖 depts 文件,而不仅仅是以小写形式显示,则需要执行以下操作:

$ awk '{print tolower($0)}' depts > depts-
$ mv depts- depts

但是,使用 sed 进行更改,你可以避免最后一步,因为 sed 可以“原地”编辑文件,如下所示,文件完整,但文本全部小写:

$ sed 's/[A-Z]/\L&/g' depts

仅将首字母转换为大写

要仅将字符串中单词的首字母转换为大写,那么可以执行以下操作:

$ echo design \& engineering| sed -e "s/\b\(.\)/\u\1/g"
Design & Engineering

该命令将确保首字母大写,但不会更改其余字母。

确保只有首字母大写

当要更改文本以使只有首字母大写时,这更具挑战性。假设你正在处理一个工作人员姓名列表,并且希望以正常的“名 姓”方式对其格式化。

使用 sed

你可以使用更复杂的 sed 命令来确保以下结果:

$ echo design \& ENGINEERING | sed 's/\b\([[:alpha:]]\)\([[:alpha:]]*\)\b/\u\1\L\2/g'
Design & Engineering

使用 Python

如果你已安装 Python,你可以运行这样的命令,它还可以设置文本格式,以便每个单词只有首字母大写,并且它可能比上面显示的 sed 命令更易于解析:

$ echo -n "design & engineering" | python3 -c "import sys; print(sys.stdin.read().title())"
Design & Engineering

有多种方法可以在大小写之间更改文本格式。哪种方法效果最好取决于你要处理的是单个字符串还是整个文件,以及想要的最终结果。


via: https://www.networkworld.com/article/3529409/converting-between-uppercase-and-lowercase-on-the-linux-command-line.html

作者:Sandra Henry-Stocker 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Signal 作为一款私人通信应用,正在变得愈发流行。而我们下面要介绍开源应用 Session 则是 Signal 的一个 复刻 fork ,它的一大亮点是并不需要提供手机号码即可以开始使用。

Session:一款真正意义上的私人通信应用

对于私人通信服务来说,有没有既能保护通信安全性,又尊重用户跨平台隐私的集大成者呢?很多注重个人隐私的用户似乎都在寻找这个问题的答案。

最近,我留意到 Loki 基金会开发的一款叫做 Session 的开源通信应用。从技术上来说,Session 是另一款开源、加密的通信应用 Signal 的一个复刻。

在本文中,我会讲述我自己使用 Session 的体验,以及 Session 的一些主要功能。

Session 在这个领域中算是一款比较新的应用了,因此我还会在文章的最后提到它的一些不足之处。

Session 的一些主要功能

接下来我会重点介绍 Session 的主要功能,可以供你参考这款应用是否值得使用。

Session 的使用过程中不需要提供手机号码

在 Signal 或者其它类似的通信应用中,用户都需要提供手机号码才得以成功注册。注重隐私的用户们都认为这样的做法会潜藏着巨大的安全隐患。

而使用 Session 则简单得多。在 PC 或手机上安装应用之后,只需要点击“ 创建账号 Create Account ”,无须提供手机号码,它就会生成一个类似 05652245af9a8bfee4f5a8138fd5c….. 这样的随机且唯一的 Session ID。

此后,把 Session ID 分享给想要添加的联系人就可以了。Session 还支持二维码,其他人可以通过扫描二维码添加你的 Session ID 为好友。

Session 使用了区块链等加密技术

Session ID

区块链有所了解的用户都很期待区块链能为普罗大众做出什么有实际意义的应用,而 Session 可以算得上其中一个。尽管 Session 的核心是基于区块链的,但普通用户在使用时并不需要真正弄懂区块链。

如果你好奇它的工作原理,可以参考这篇官方的博客文章,里面有相关的解释。

跨平台支持

这样严格保护隐私的应用,是否能在不同平台上使用?

答案是肯定的。首先,它支持 Linux 和 Android 平台,同时也支持 Windows/Mac/iOS 平台。因此跨平台、跨设备的消息同步是没有问题的。

包含基本隐私选项

毫无疑问,基本的隐私功能是必须有的,这是作为一个以安全为卖点的应用所必备的体验。

最基本的选项包括:

  • 消息有效期:你可以控制一条消息在接收者阅读前的保留时长
  • 已读回执:消息发送者可以知晓你已经阅读该消息

Session 使用去中心化网络保护你的元数据

尽管 Session 不使用 端对端 peer-to-peer 技术,但它也不使用中心化的服务器。

Session 采用了去中心化的架构实现消息的传输和路由。如果你不熟悉这方面的内容,可以关注 Session 的官方博客,尝试了解中心化网络和去中心化网络的区别,以及它的实际工作原理。

同时,这样的网络架构还有助于保护诸如与 IP 地址相关的信息等元数据。

其它功能

除了专注于隐私之外,Session 也支持群聊、语音消息、发送附件等通信应用的基本功能。

在 Linux 上安装 Session

官方下载页面中可以下载到对应的 .AppImage 文件。如果你不了解这个文件的使用方法,可以查阅我们的相关文章

另外,你也可以在它的 Github 发布页面 获取到对应的 .deb 安装文件。

我使用 Session 的体验

我在各种平台上都试用过 Session,其中在 PC 上我使用了 Pop!\_OS 19.10 的 .AppImage 文件运行这个应用。

总的来说,使用的体验很不错,用户界面也没有出现问题。

在设置中备份了密码(也称为 种子 seed )后,可以很方便地恢复账号。

当然,我也发现了一些需要改进的地方:

  • 在接受好友请求时会出现延迟
  • 设备间连接的方式不太直观
  • 当你在不同的设备上使用同一个 Session ID 向同一个人回复消息时,对方会收到两个不同的对话

总结

当然,最完美的事物是不存在的。我也会一直使用 Session 并考虑它发展的方向,这是一个注重隐私的用户应该做的事情。

欢迎在评论区发表你的看法。


via: https://itsfoss.com/session-messenger/

作者:Ankush Das 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解 Rust 的软件包管理器和构建工具。

Rust 是一种现代编程语言,可提供高性能、可靠性和生产力。几年来,它一直被 StackOverflow 调查评为最受欢迎的语言

除了是一种出色的编程语言之外,Rust 还具有一个称为 Cargo 的构建系统和软件包管理器。Cargo 处理许多任务,例如构建代码、下载库或依赖项等等。这两者捆绑在一起,因此在安装 Rust 时会得到 Cargo。

安装 Rust 和 Cargo

在开始之前,你需要安装 Rust 和 Cargo。Rust 项目提供了一个可下载的脚本来处理安装。要获取该脚本,请打开浏览器以访问 https://sh.rustup.rs 并保存该文件。阅读该脚本以确保你对它的具体行为有所了解,然后再运行它:

$ sh ./rustup.rs

你也可以参考这个安装 Rust 的网页以获取更多信息。

安装 Rust 和 Cargo 之后,你必须 获取 source env 文件中的配置:

$ source $HOME/.cargo/env

更好的办法是,将所需目录添加到 PATH 环境变量中:

export PATH=$PATH:~/.cargo/bin

如果你更喜欢使用软件包管理器(例如 Linux 上的 DNF 或 Apt),请在发行版本的存储库中查找 Rust 和 Cargo 软件包,并进行相应的安装。 例如:

$ dnf install rust cargo

安装并设置它们后,请验证你拥有的 Rust 和 Cargo 版本:

$ rustc --version
rustc 1.41.0 (5e1a79984 2020-01-27)
$ cargo --version
cargo 1.41.0 (626f0f40e 2019-12-03)

手动构建和运行 Rust

从在屏幕上打印“Hello, world!”的简单程序开始。打开你喜欢的文本编辑器,然后键入以下程序:

$ cat hello.rs
fn main() {
    println!("Hello, world!");
}

以扩展名 .rs 保存文件,以将其标识为 Rust 源代码文件。

使用 Rust 编译器 rustc 编译程序:

$ rustc hello.rs

编译后,你将拥有一个与源程序同名的二进制文件:

$ ls -l
total 2592
-rwxr-xr-x. 1 user group 2647944 Feb 13 14:14 hello
-rw-r--r--. 1 user group      45 Feb 13 14:14 hello.rs
$

执行程序以验证其是否按预期运行:

$ ./hello
Hello, world!

这些步骤对于较小的程序或任何你想快速测试的东西就足够了。但是,在进行涉及到多人的大型程序时,Cargo 是前进的最佳之路。

使用 Cargo 创建新包

Cargo 是 Rust 的构建系统和包管理器。它可以帮助开发人员下载和管理依赖项,并帮助创建 Rust 包。在 Rust 社区中,Rust 中的“包”通常被称为“crate”(板条箱),但是在本文中,这两个词是可以互换的。请参阅 Rust 社区提供的 Cargo FAQ 来区分。

如果你需要有关 Cargo 命令行实用程序的任何帮助,请使用 --help-h 命令行参数:

$ cargo –help

要创建一个新的包,请使用关键字 new,跟上包名称。在这个例子中,使用 hello_opensource 作为新的包名称。运行该命令后,你将看到一条消息,确认 Cargo 已创建具有给定名称的二进制包:

$ cargo new hello_opensource
     Created binary (application) `hello_opensource` package

运行 tree 命令以查看目录结构,它会报告已创建了一些文件和目录。首先,它创建一个带有包名称的目录,并且在该目录内有一个存放你的源代码文件的 src 目录:

$ tree .
.
└── hello_opensource
    ├── Cargo.toml
    └── src
        └── main.rs

2 directories, 2 files

Cargo 不仅可以创建包,它也创建了一个简单的 “Hello, world” 程序。打开 main.rs 文件看看:

$ cat hello_opensource/src/main.rs
fn main() {
    println!("Hello, world!");
}

下一个要处理的文件是 Cargo.toml,这是你的包的配置文件。它包含有关包的信息,例如其名称、版本、作者信息和 Rust 版本信息。

程序通常依赖于外部库或依赖项来运行,这使你可以编写应用程序来执行不知道如何编码或不想花时间编码的任务。你所有的依赖项都将在此文件中列出。此时,你的新程序还没有任何依赖关系。打开 Cargo.toml 文件并查看其内容:

$ cat hello_opensource/Cargo.toml
[package]
name = "hello_opensource"
version = "0.1.0"
authors = ["user <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

使用 Cargo 构建程序

到目前为止,一切都很顺利。现在你已经有了一个包,可构建一个二进制文件(也称为可执行文件)。在此之前,进入包目录:

$ cd hello_opensource/

你可以使用 Cargo 的 build 命令来构建包。注意消息说它正在“编译”你的程序:

$ cargo build
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.38s

运行 build 命令后,检查项目目录发生了什么:

$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    └── debug
        ├── build
        ├── deps
        │   ├── hello_opensource-147b8a0f466515dd
        │   └── hello_opensource-147b8a0f466515dd.d
        ├── examples
        ├── hello_opensource
        ├── hello_opensource.d
        └── incremental
            └── hello_opensource-3pouh4i8ttpvz
                ├── s-fkmhjmt8tj-x962ep-1hivstog8wvf
                │   ├── 1r37g6m45p8rx66m.o
                │   ├── 2469ykny0eqo592v.o
                │   ├── 2g5i2x8ie8zed30i.o
                │   ├── 2yrvd7azhgjog6zy.o
                │   ├── 3g9rrdr4hyk76jtd.o
                │   ├── dep-graph.bin
                │   ├── query-cache.bin
                │   ├── work-products.bin
                │   └── wqif2s56aj0qtct.o
                └── s-fkmhjmt8tj-x962ep.lock

9 directories, 17 files

哇!编译过程产生了许多中间文件。另外,你的二进制文件将以与软件包相同的名称保存在 ./target/debug 目录中。

使用 Cargo 运行你的应用程序

现在你的二进制文件已经构建好了,使用 Cargo 的 run 命令运行它。如预期的那样,它将在屏幕上打印 Hello, world!

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/hello_opensource`
Hello, world!

或者,你可以直接运行二进制文件,该文件位于:

$ ls -l ./target/debug/hello_opensource
-rwxr-xr-x. 2 root root 2655552 Feb 13 14:19 ./target/debug/hello_opensource

如预期的那样,它产生相同的结果:

$ ./target/debug/hello_opensource
Hello, world!

假设你需要重建包,并丢弃早期编译过程创建的所有二进制文件和中间文件。Cargo 提供了一个方便的clean 选项来删除所有中间文件,但源代码和其他必需文件除外:

$ cargo clean
$ tree .
.
├── Cargo.lock
├── Cargo.toml
└── src
    └── main.rs

1 directory, 3 files

对程序进行一些更改,然后再次运行以查看其工作方式。例如,下面这个较小的更改将 Opensource 添加到 Hello, world! 字符串中:

$ cat src/main.rs
fn main() {
    println!("Hello, Opensource world!");
}

现在,构建该程序并再次运行它。这次,你会在屏幕上看到 Hello, Opensource world!

$ cargo build
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/hello_opensource`
Hello, Opensource world!

使用 Cargo 添加依赖项

Cargo 允许你添加程序需要运行的依赖项。使用 Cargo 添加依赖项非常容易。每个 Rust 包都包含一个 Cargo.toml 文件,其中包含一个依赖关系列表(默认为空)。用你喜欢的文本编辑器打开该文件,找到 [dependencies] 部分,然后添加要包含在包中的库。例如,将 rand 库添加为依赖项:

$ cat Cargo.toml
[package]
name = "hello_opensource"
version = "0.1.0"
authors = ["test user <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.3.14"

试试构建你的包,看看会发生什么。

$ cargo build
    Updating crates.io index
   Compiling libc v0.2.66
   Compiling rand v0.4.6
   Compiling rand v0.3.23
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 4.48s

现在,Cargo 会联系 Crates.io(这是 Rust 用于存储 crate(或包)的中央仓库),并下载和编译 rand。但是,等等 —— libc 包是怎么回事?你没有要安装 libc 啊。是的,rand 包依赖于 libc 包;因此,Cargo 也会下载并编译 libc

库的新版本会不断涌现,而 Cargo 提供了一种使用 update 命令更新其所有依赖关系的简便方法:

cargo update

你还可以选择使用 -p 标志跟上包名称来更新特定的库:

cargo update -p rand

使用单个命令进行编译和运行

到目前为止,每当对程序进行更改时,都先使用了 build 之后是 run。有一个更简单的方法:你可以直接使用 run 命令,该命令会在内部进行编译并运行该程序。要查看其工作原理,请首先清理你的软件包目录:

$ cargo clean
$ tree .
.
├── Cargo.lock
├── Cargo.toml
└── src
    └── main.rs

1 directory, 3 files

现在执行 run。输出信息表明它已进行编译,然后运行了该程序,这意味着你不需要每次都显式地运行 build

$ cargo run
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.41s
     Running `target/debug/hello_opensource`
Hello, world!

在开发过程中检查代码

在开发程序时,你经常会经历多次迭代。你需要确保你的程序没有编码错误并且可以正常编译。你不需要负担在每次编译时生成二进制文件的开销。Cargo 为你提供了一个 check 选项,该选项可以编译代码,但跳过了生成可执行文件的最后一步。首先在包目录中运行 cargo clean

$ tree .
.
├── Cargo.lock
├── Cargo.toml
└── src
    └── main.rs

1 directory, 3 files

现在运行 check 命令,查看对目录进行了哪些更改:

$ cargo check
    Checking hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s

该输出显示,即使在编译过程中创建了中间文件,但没有创建最终的二进制文件或可执行文件。这样可以节省一些时间,如果该包包含了数千行代码,这非常重要:

$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    └── debug
        ├── build
        ├── deps
        │   ├── hello_opensource-842d9a06b2b6a19b.d
        │   └── libhello_opensource-842d9a06b2b6a19b.rmeta
        ├── examples
        └── incremental
            └── hello_opensource-1m3f8arxhgo1u
                ├── s-fkmhw18fjk-542o8d-18nukzzq7hpxe
                │   ├── dep-graph.bin
                │   ├── query-cache.bin
                │   └── work-products.bin
                └── s-fkmhw18fjk-542o8d.lock

9 directories, 9 files

要查看你是否真的节省了时间,请对 buildcheck 命令进行计时并进行比较。首先,计时 build 命令:

$ time cargo build
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.40s

real    0m0.416s
user    0m0.251s
sys     0m0.199s

在运行 check 命令之前清理目录:

$ cargo clean

计时 check 命令:

$ time cargo check
    Checking hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s

real    0m0.166s
user    0m0.086s
sys     0m0.081s

显然,check 命令要快得多。

建立外部 Rust 包

到目前为止,你所做的这些都可以应用于你从互联网上获得的任何 Rust crate。你只需要下载或克隆存储库,移至包文件夹,然后运行 build 命令,就可以了:

git clone <github-like-url>
cd <package-folder>
cargo build

使用 Cargo 构建优化的 Rust 程序

到目前为止,你已经多次运行 build,但是你注意到它的输出了吗?不用担心,再次构建它并密切注意:

$ cargo build
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished dev [unoptimized + debuginfo] target(s) in 0.36s

看到了每次编译后的 [unoptimized + debuginfo] 文本了吗?这意味着 Cargo 生成的二进制文件包含大量调试信息,并且未针对执行进行优化。开发人员经常经历开发的多次迭代,并且需要此调试信息进行分析。同样,性能并不是开发软件时的近期目标。因此,对于现在而言是没问题的。

但是,一旦准备好发布软件,就不再需要这些调试信息。而是需要对其进行优化以获得最佳性能。在开发的最后阶段,可以将 --release 标志与 build 一起使用。仔细看,编译后,你应该会看到 [optimized] 文本:

$ cargo build --release
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
    Finished release [optimized] target(s) in 0.29s

如果愿意,你可以通过这种练习来了解运行优化软件与未优化软件时节省的时间。

使用 Cargo 创建库还是二进制文件

任何软件程序都可以粗略地分类为独立二进制文件或库。一个独立二进制文件也许即使是当做外部库使用的,自身也是可以运行的。但是,作为一个库,是可以被另一个独立二进制文件所利用的。到目前为止,你在本教程中构建的所有程序都是独立二进制文件,因为这是 Cargo 的默认设置。 要创建一个,请添加 --lib 选项:

$ cargo new --lib libhello
     Created library `libhello` package

这次,Cargo 不会创建 main.rs 文件,而是创建一个 lib.rs 文件。 你的库的代码应该是这样的:

$ tree .
.
└── libhello
    ├── Cargo.toml
    └── src
        └── lib.rs

2 directories, 2 files

Cargo 就是这样的,不要奇怪,它在你的新库文件中添加了一些代码。通过移至包目录并查看文件来查找添加的内容。默认情况下,Cargo 在库文件中放置一个测试函数。

使用 Cargo 运行测试

Rust 为单元测试和集成测试提供了一流的支持,而 Cargo 允许你执行以下任何测试:

$ cd libhello/

$ cat src/lib.rs
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

Cargo 有一个方便的 test 命令,可以运行代码中存在的任何测试。尝试默认运行 Cargo 在库代码中放入的测试:

$ cargo test
   Compiling libhello v0.1.0 (/opensource/libhello)
    Finished test [unoptimized + debuginfo] target(s) in 0.55s
     Running target/debug/deps/libhello-d52e35bb47939653

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests libhello

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

深入了解 Cargo 内部

你可能有兴趣了解在运行一个 Cargo 命令时它底下发生了什么。毕竟,在许多方面,Cargo 只是个封装器。要了解它在做什么,你可以将 -v 选项与任何 Cargo 命令一起使用,以将详细信息输出到屏幕。

这是使用 -v 选项运行 buildclean 的几个例子。

build 命令中,你可以看到这些给定的命令行选项触发了底层的 rustc(Rust 编译器):

$ cargo build -v
   Compiling hello_opensource v0.1.0 (/opensource/hello_opensource)
     Running `rustc --edition=2018 --crate-name hello_opensource src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C debuginfo=2 -C metadata=147b8a0f466515dd -C extra-filename=-147b8a0f466515dd --out-dir /opensource/hello_opensource/target/debug/deps -C incremental=/opensource/hello_opensource/target/debug/incremental -L dependency=/opensource/hello_opensource/target/debug/deps`
    Finished dev [unoptimized + debuginfo] target(s) in 0.36s

clean 命令表明它只是删除了包含中间文件和二进制文件的目录:

$ cargo clean -v
    Removing /opensource/hello_opensource/target

不要让你的技能生锈

要扩展你的技能,请尝试使用 Rust 和 Cargo 编写并运行一个稍微复杂的程序。很简单就可以做到:例如,尝试列出当前目录中的所有文件(可以用 9 行代码完成),或者尝试自己回显输入。小型的实践应用程序可帮助你熟悉语法以及编写和测试代码的过程。

本文为刚起步的 Rust 程序员提供了大量信息,以使他们可以开始入门 Cargo。但是,当你开始处理更大、更复杂的程序时,你需要对 Cargo 有更深入的了解。当你准备好迎接更多内容时,请下载并阅读 Rust 团队编写的开源的《Cargo 手册》,看看你可以创造什么!


via: https://opensource.com/article/20/3/rust-cargo

作者:Gaurav Kamathe 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

GIMP 是最流行的自由开源的图像编辑器,它也许是 Linux 上最好的 Adobe Photoshop 替代品

当你在 Ubuntu 或其他任何操作系统上安装了 GIMP 后,你会发现已经安装了一些用于基本图像编辑的画笔。如果你需要更具体的画笔,你可以随时在 GIMP 中添加新画笔。

怎么样?让我在这个快速教程中向你展示。

如何在 GIMP 中添加画笔

在 GIMP 中安装新画笔需要三个步骤:

  • 获取新画笔
  • 将其放入指定的文件夹中
  • 刷新 GIMP 中的画笔

步骤 1:下载新的 GIMP 画笔

第一步是获取新的 GIMP 画笔。你从哪里获取?当然是从互联网上。

你可以在 Google 或如 Duck Duck Go 这种隐私搜索引擎来搜索 “GIMP brushes”,并从网站下载一个你喜欢的。

GIMP 画笔通常以 .gbr 和 .gih 文件格式提供。.gbr 文件用于常规画笔,而 .gih 用于动画画笔。

你知道吗?

从 2.4 版本起,GIMP 使安装和使用 Photoshop 画笔(.abr 文件)非常简单。你只需将 Photoshop 画笔文件放在正确的文件夹中。

请记住,最新的 Photoshop 画笔可能无法完美地在 GIMP 中使用。

步骤 2:将新画笔复制到它的位置

获取画笔文件后,下一步是复制该文件并将其粘贴到 GIMP 配置目录中所在的文件夹。

在微软 Windows 上,你必须进入类似 C:\Documents and Settings\myusername.gimp-2.10\brushes 这样的文件夹。

我将展示 Linux 上的详细步骤,因为我们是一个专注于 Linux 的网站。

选择画笔文件后,在家目录中按下 Ctrl+h 查看 Linux 中的隐藏文件

Press Ctrl+H to see hidden files in the home directory

你应该进入 .config/GIMP/2.10/brushes 文件夹(如果你使用的是 GIMP 2.10)。如果使用其他版本,那么应在 .config/GIMP 下看到相应文件夹。

Adding New Brushes in GIMP

将画笔文件粘贴到此文件夹中。可选地,你可以通过再次按 Ctrl+h 来隐藏隐藏的文件。

步骤 3:刷新画笔(避免重启 GIMP)

GIMP 将在启动时自动加载画笔。如果已在运行,并且不想关闭它,你可以刷新画笔。

在 GIMP 的主菜单中找到 “Windows->Dockable Dialogues->Brushes”。

Refresh GIMP Brushes by going go to Windows-&gt;Dockable Dialogues-&gt; Brushes

在右侧栏的 Brushes 对话框中找到“refresh”图标。

Refresh GIMP Brushes

如果你的画笔没有出现,那么你可以试试重启 GIMP。

额外的技巧!

GIMP 中添加新画笔还能让你轻松给图片添加水印。只需将 logo 用作画笔,并点击一下就可添加到图片中。

我希望你喜欢这个快速 GIMP 技巧。敬请期待更多。


via: https://itsfoss.com/add-brushes-gimp/

作者:Community 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

即使你的操作系统是闭源的,你仍然可以使用这个流行的开源文本编辑器。

GNU Emacs 是一个专为各种程序员设计的流行的文本编辑器。因为它是在 Unix 上开发的,并在 Linux(macOS 中也有)上得到了广泛使用,所以人们有时没有意识到它也可用于微软 Windows 上。你也无需成为有经验的或专职的程序员即可使用 Emacs。只需单击几下就可以下载并安装 Emacs,本文向你展示了如何进行。

你可以手动安装 Windows,也可以使用包管理器安装,例如 Chocolatey

7-zip

如果还没在 Windows 中安装 7-zip,那么就先安装它。7-zip 是一个开源的归档程序,能够创建和解压 ZIP、7z、TAR、XZ、BZIP2 和 GZIP(以及更多)文件。对于 Windows 用户来说,这是一个宝贵的工具。

安装 7-zip 后,在 Windows 资源管理器中浏览文件时,右键单击菜单中就有新的 7-zip 归档选项。

Powershell 和 Chocolatey

要在 Windows 上使用 Chocolatey 安装 GNU Emacs :

PS> choco install emacs-full

安装后,在 Powershell 中启动 Emacs:

PS> emacs

 title=

下载适用于 Windows 的 GNU Emacs

要在 Windows 上手动安装 GNU Emacs,你必须下载 Emacs

 title=

它会打开连接到离你最近的服务器,并展示所有可用的 Emacs 版本。找到发行版本号最高的目录,然后单击进入。Windows 有许多不同的 Emacs 构建,但是最通用的版本只是被命名为 emacs-VERSION-ARCHITECTURE.zipVERSION 取决于你要下载的版本,而 ARCHITECTURE 取决于你使用的是 32 位还是 64 位计算机。大多数现代计算机都是 64 位的,但是如果你有疑问,可以下载 32 位版本,它可在两者上运行。

如果要下载 64 位计算机的 Emacs v26,你应该点击 emacs-26.2-x86_64.zip 的链接。有较小的下载包(例如 “no-deps” 等),但是你必须熟悉如何从源码构建 Emacs,知道它需要哪些库以及你的计算机上已经拥有哪些库。通常,获取较大版本的 Emacs 最容易,因为它包含了在计算机上运行所需的一切。

解压 Emacs

接下来,解压下载的 ZIP 文件。要解压缩,请右键单击 Emacs ZIP 文件,然后从 7-zip 子菜单中选择 “Extract to Emacs-VERSION”。这是一个很大的压缩包,因此解压可能需要一段时间,但是完成后,你将拥有一个新目录,其中包含与 Emacs 一起分发的所有文件。例如,在此例中,下载了 emacs-26.2-x86_64.zip,因此解压后的目录为 emacs-26.2-x86_64

启动 Emacs

在 Emacs 目录中,找到 bin 目录。此文件夹存储随 Emacs 一起分发的所有二进制可执行文件(EXE 文件)。双击 emacs.exe 文件启动应用。

 title=

你可以在桌面上创建 emacs.exe 的快捷方式,以便于访问。

学习 Emacs

Emacs 并不像传闻那样难用。它具有自己的传统和惯例,但是当你其中输入文本时,你可以像在记事本或者网站的文本框中那样使用它。

重要的区别是在你编辑输入的文本时。

但是,学习的唯一方法是开始使用它,因此,使 Emacs 成为完成简单任务的首选文本编辑器。当你通常打开记事本、Word 或 Evernote 或其他工具来做快速笔记或临时记录时,请启动 Emacs。

Emacs 以基于终端的应用而闻名,但它显然有 GUI,因此请像使用其他程序一样经常使用它的 GUI。从菜单而不是使用键盘复制、剪切和粘贴(paste)(或用 Emacs 的术语 “yank”),然后从菜单或工具栏打开和保存文件。从头开始,并根据应用本身来学习它,而不是根据你以往对其他编辑器的经验就认为它应该是怎样。

感谢 Matthias Pfuetzner 和 Stephen Smoogen。


via: https://opensource.com/article/20/3/emacs-windows

作者:Seth Kenlon 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出